File uploads
Intro
If you decide how to upload files via some REST endpoint or GraphQL. So I recommend to upload via some REST API and then provide a path of the uploaded file to your mutation request. GraphQL designed to provide typed data according to client request shape. With files (binary data) it works too, but better to do it via well-recommended REST calls. In such case, you separate highly costed upload logic from data manipulation logic. In the future, this will help you diagnose problems with the load more easily.
Anyway products have different scenarios and you may be forced to upload files via GraphQL. For uploading files via GraphQL you will need:
- graphql-multipart-request-spec - will be good if you get acquainted with this spec
- apollo-upload-server - for parsing
multipart/form-dataPOST requests via busboy and providingFiles data toresolvefunction as argument.
Tutorial
1. Preparing express-graphql server
This is most important part of enabling file uploads on server-side. You need to parse body data via bodyParser.json() and multipart form data via apolloUploadExpress(/* Options */).
import express from 'express';
import graphqlHTTP from 'express-graphql';
+ import bodyParser from 'body-parser';
+ import { apolloUploadExpress } from 'apollo-upload-server';
import { schema } from './schema';
const PORT = 4000;
const app = express();
app.use(
'/graphql',
+ bodyParser.json(),
+ apolloUploadExpress(/* Options */),
graphqlHTTP(async (request, response, graphQLParams) => {
return {
schema,
graphiql: true,
context: {
req: request,
},
};
})
);
2. Adding GraphQLUpload scalar type to graphql-compose
You may add 3rd part GraphQL types to graphql-compose. And make it very easy:
import { TypeComposer, schemaComposer } from 'graphql-compose';
import { GraphQLUpload } from 'apollo-upload-server';
schemaComposer.set('Upload', GraphQLUpload);
3. Writing you first mutation with file uploads
graphql-compose supports very convinient syntaxt of field definitions. Let's write our first mutation with file uploads:
schemaComposer.Mutation.addFields({
createPost: {
type: 'Post',
args: {
id: 'Int!',
title: 'String',
authorId: 'Int',
images: '[Upload]',
poster: 'Upload',
},
resolve: async (_, { id, title, authorId, images, poster }) => {
const newPost = { id, title, authorId };
// somehow work with files
if (poster) {
console.log(poster);
console.log(await poster);
}
// somehow save a new record
posts.push(newPost);
return newPost;
},
},
});
4. Properly sending files from the client
This is a most problematic part and it's out of scope of graphql-compose (it's client-side problem). You must correctly send HTTP request from the client. But if you very carefully read graphql-multipart-request-spec, then you should not have any questions.
Here's an example of proper multipart/form-data POST request with
operationskey for GraphQL request withqueryandvariablesmapkey with mapping somemultipart-datato exact GraphQL variable- and other keys for
multipart-datawhich contains binary data of files
Request via CURL
curl localhost:4000/graphql \
-F operations='{ "query": "mutation ($poster: Upload) { createPost(id: 5, poster: $poster) { id } }", "variables": { "poster": null } }' \
-F map='{ "0": ["variables.poster"] }' \
-F 0=@package.json
Request via Altair
Altair GraphQL Client supports file upload using the graphql-multipart-request-spec so you can also use it to make a request.
Response from GraphQL server
{"data":{"createPost":{"id":5}}}
Request via POSTMAN

Console log on the server side
Argument `poster` is a Promise:
Promise {
{ stream:
FileStream {
_readableState: [Object],
readable: true,
domain: null,
_events: [Object],
_eventsCount: 2,
_maxListeners: undefined,
truncated: false,
_read: [Function] },
filename: 'package.json',
mimetype: 'application/octet-stream',
encoding: '7bit' } }
It's value is and object with FileStream:
{ stream:
FileStream {
_readableState:
ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: [Object],
length: 1983,
pipes: null,
pipesCount: 0,
flowing: null,
ended: true,
endEmitted: false,
reading: false,
sync: false,
needReadable: false,
emittedReadable: true,
readableListening: false,
resumeScheduled: false,
destroyed: false,
defaultEncoding: 'utf8',
awaitDrain: 0,
readingMore: false,
decoder: null,
encoding: null },
readable: true,
domain: null,
_events: { end: [Array], limit: [Object] },
_eventsCount: 2,
_maxListeners: undefined,
truncated: false,
_read: [Function] },
filename: 'package.json',
mimetype: 'application/octet-stream',
encoding: '7bit' }
Demo Repo
You may take a look at graphql-compose-boilerplate-upload and its easy to follow commits for understanding how to allow file uploads in graphql-compose.
