How to Upload Images with GraphQL and MongoDB
Uploading images in a web application often requires handling file streams, converting them into a storable format, and persisting them in a database. This guide demonstrates how to achieve this with GraphQL, MongoDB, and Node.js.
Step 1: Define the Image Schema
We’ll use Mongoose to define a schema for storing image data in MongoDB:
const mongoose = require(“mongoose”); const { Schema } = mongoose; const imageSchema = new Schema({ image: { type: Buffer }, filename: { type: String }, mimetype: { type: String }, }); const ImageModel = mongoose.model(“Image”, imageSchema); |
This schema includes:
- image: The binary data of the image.
- filename: The name of the file.
- mimetype: The file type (e.g., image/jpeg).
Step 2: Implement Image Upload Logic
Create a utility function to convert an uploaded image into a Base64 format:
const convertImageToBase64 = async (image: any): Promise<string | null> => { if (image && image.promise) { const { createReadStream } = await image.promise; const stream = createReadStream(); return new Promise<Buffer>((resolve, reject) => { const chunks: Buffer[] = []; stream.on(“data”, (chunk: Buffer) => chunks.push(chunk)); stream.on(“end”, () => resolve(Buffer.concat(chunks))); stream.on(“error”, reject); }).then((imageBuffer) => imageBuffer.toString(“base64”)); } return null; }; |
Step 3: Write the GraphQL Mutation
Create a mutation to handle the image upload:
const { GraphQLUpload } = require(“graphql-upload”); const { ApolloServer, gql } = require(“apollo-server”); const typeDefs = gql` scalar Upload type ImageResponse { id: ID! filename: String! mimetype: String! } type Mutation { addImage(image: Upload!): ImageResponse } `; const resolvers = { Upload: GraphQLUpload, Mutation: { addImage: async (_: any, { image }: { image: any }) => { if (!image) throw new Error(“No image provided”); const base64Image = await convertImageToBase64(image); const filename = image.file?.filename; const mimetype = image.file?.mimetype; if (!base64Image || !filename || !mimetype) { throw new Error(“Invalid image format”); } const newImage = new ImageModel({ image: Buffer.from(base64Image, “base64”), filename, mimetype, }); await newImage.save(); return { id: newImage.id, filename: newImage.filename, mimetype: newImage.mimetype, }; }, }, }; const server = new ApolloServer({ typeDefs, resolvers }); server.listen().then(({ url }: { url: string }) => { console.log(`Server ready at ${url}`); }); |
Step 4: Mutation
To test the image upload, use a GraphQL client in Postman. Here’s an example mutation:
mutation UploadImage($image: Upload!) { addImage(image: $image) { id filename mimetype } } |
In your HTTP request to the GraphQL endpoint (e.g., http://localhost:4000/graphql), use the following structure in the postman
Operations
{ “query”: “mutation addImage($image: Upload) { addImage(image: $image){ id filename mimetype } }”, “variables”: { “image”: null } } |
Map
{ “0”: [“variables.image”] } |
Files
The file data (e.g., image.jpg) should be sent under key 0 as defined in the map.
Step 5: Update Contextual Mutation
If you’re using a mutation with a slightly different structure, such as:
mutation addImage($image: Upload) { addImage(image: $image) { success message } } |
Adjust your GraphQL client request to:
Operations
{ “query”: “mutation addImage($image: Upload) { editPayment(image: $image) { success message } }”, “variables”: { “image”: null } } |
Map
{ “0”: [“variables.image”] } |
Conclusion
This implementation demonstrates how to upload images using GraphQL and store them in MongoDB. By leveraging Mongoose for database interactions and Node.js streams for efficient file handling, you can build a scalable and efficient image upload feature. Customize your mutation as needed to fit your application’s requirements.