single file upload in GraphQL API

single file upload in GraphQL API

In this guide, we will upload a single file to the database using GraphQL, NodeJS, Express, FileSystem, and MongoDB. So, let's start by setting up our project environment.

npm init -y

In this tutorial, I will use npm, you can use yarn if you want to. It's really up to you, anyone is fine.

Next, structure your project as follows:

├── node_modules
├── graphql
|  ├── resolvers
|  |  └── index.js
|  └── typeDefs.js
├── models
|  └── File.js
├── index.js
└── package.json

install other packages:

npm i graphql mongoose express path body-parser

After installing the necessary packages, the package.json file looks like:

{
  "name": " ",
  "main": "index.js",
  "scripts": {
    "start": "node ./src/index.js",
  },
  "author": "",
  "license": "ISC",
  "homepage": "",
  "dependencies": {
    "apollo-server": "^2.11.0",
    "body-parser": "^1.18.3",
    "express": "^4.16.3",
    "mongoose": "^5.9.7",
    "path": "^0.12.7",
  }
}

Then, create your type definition for file upload in the typeDef.js file. You can check GraphQL docs to learn more about type definition.

import { gql } from 'apollo-server';

const typeDefs = gql`
type File {
    id: ID!
    filename: String!
    mimetype: String!
    path: String!
  }

type Mutation { 
uploadFile(file: Upload!): File
}
`;

export default typeDefs;

You might be wondering where the Upload! type comes from, its actually from graphql, its in-built. Then, you can go ahead to create a model for the image in the File.js file inside the model folder.

import { model, Schema } from 'mongoose';

const fileSchema = new Schema({
  filename: String,
  mimetype: String,
  path: String,
});

const File = model('File', fileSchema);

export default File;

Then, we can open the index.js file inside the resolver folder and create the logic we will be using to interact with the database.

import { createWriteStream } from 'fs';
import File from '../../models/File';

const storeUpload = async ({ stream, filename, mimetype }) => {
  const path = `${filename}`;
  return new Promise((resolve, reject) =>
    stream
      .pipe(createWriteStream(path))
      .on('finish', () => resolve({ path, filename, mimetype }))
      .on('error', reject)
  );
};
const processUpload = async upload => {
  const { createReadStream, filename, mimetype } = await upload;
  const stream = createReadStream();
  const file = await storeUpload({ stream, filename, mimetype });
  return file;
};

const resolvers = {
  Mutation: {
 async uploadFile(_, { file }, context) {
      try {
        const upload = await processUpload(file);
        const { path, filename, mimetype } = upload;
        const newFile = new File({
          path,
          filename,
          mimetype,
        });
        const res = await newFile.save();
        return {
          ...res._doc,
        };
      } catch (err) {
        throw new Error('profile photo upload failed');
      }
    },
  }
};

export default resolvers;

You will notice that there are two functions for processing the file using fs. Note: you can decide to use any storage you want e.g cloudinary, AWS s3, etc. I decided to use fs module because I don't want to have separate storage for users' files.

After this, we can make a request via CURL or postman to test the code. using CURL:

curl localhost:4000/graphql \
  -F operations='{ "query": "mutation ($file: Upload) { uploadFile((file: $file) }", "variables": { "file": null } }' \
  -F map='{ "0": ["variables.file"] }' \
  -F 0=@package.json

using Postman:

request.png By now you should be able to see the link to the file in the database as a string. To learn more check GraphQL multi-part request .

I hope this helps. Thank you for reading.