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.

Leave a Reply