File Uploads with Apollo Server

Learn how to upload files with Apollo Server

How it works

The upload functionality follows the GraphQL multipart form requests specification. Two parts are needed to make the upload work correctly. The server and the client:

The Client: On the client, file objects are mapped into a mutation and sent to the server in a multipart request.

The Server: The multipart request is received. The server processes it and provides an upload argument to a resolver. In the resolver function, the upload promise resolves an object.

Server Configuration

The default option for enabling file uploads in Apollo Server involves creating a schema and using the Upload type like so:

const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  scalar Upload
  type File {
    filename: String!
    mimetype: String!
    encoding: String!
  }
  type Query {
    uploads: [File]
  }
  type Mutation {
    singleUpload(file: Upload!): File!
  }
`;

Apollo Server 1.0 we have to add the Upload scalar to the schema.

Apollo Server 2.0 automatically adds the Upload scalar to the schema, when you are not setting the schema manually.

Resolver implementation

Earlier on, I mentioned that the server returns an upload promise that resolves an object. The object contains the following:

  1. stream: The upload stream manages streaming the file(s) to a filesystem or any storage location of your choice. e.g. S3, Azure, Cloudinary, e.t.c.
  2. filename: The name of the uploaded file(s).
  3. mimetype: The MIME type of the file(s) such as text/plain, application/pdf, etc.
  4. encoding: The file encoding such as UTF-8.
const resolvers = {
  Query: {
    files: () => {
      // Return the record of files uploaded from your DB or API or filesystem.
    }
  },
  Mutation: {
    async singleUpload(parent, { file }) {
      const { stream, filename, mimetype, encoding } = await file;

      // 1. Validate file metadata.

      // 2. Stream file contents into cloud storage:
      // https://nodejs.org/api/stream.html

      // 3. Record the file upload in your DB.
      // const id = await recordFile( … )

      return { filename, mimetype, encoding };
    }
  },
};

In the code above, the file can be validated after the promise resolves. If the file size or type is right (depending on the validation technique), it can be streamed into cloud storage like Cloudinary and the returned link can be stored in a database. Otherwise an Apollo error can be thrown within the resolver.

Note: When using typeDefs, Apollo Server adds scalar Upload to your schema, so any existing declaration of scalar Upload in the type definitions should be removed. If you create your schema with makeExecutableSchema and pass it to ApolloServer constructor using the schema param, make sure to include scalar Upload.

References:

https://blog.apollographql.com/file-uploads-with-apollo-server-2-0-5db2f3f60675

https://www.apollographql.com/docs/apollo-server/data/file-uploads/