Apollo Server 3 is officially end-of-life as of 22 October 2024.

Learn more about upgrading.

File uploads

Enabling file uploads in Apollo Server


There are a few ways to accomplish file uploads in Apollo Server. Our blog post on file uploads goes in depth on the approaches we've taken, outlining the pros and cons of each.

Apollo recommends handling the file upload itself out-of-band of your GraphQL server for the sake of simplicity and security. This "signed URL" approach allows your client to retrieve a URL from your GraphQL server (via S3 or other storage service) and upload the file to the URL rather than to your GraphQL server. The blog post mentioned above goes into detail on how to set this up.

Another common approach involves adding file upload support directly to Apollo Server via the third-party graphql-upload library. This package provides support for the multipart/form-data content-type. Note: this approach is vulnerable to a CSRF mutation attack unless Apollo Server is explicitly configured with csrfPrevention: true. In the examples below, we demonstrate configuring CSRF prevention in order to prevent this vulnerability by always requiring a "preflight" OPTIONS request before executing an operation. When configuring your file upload client, you will need to send a non-empty Apollo-Require-Preflight header or Apollo Server will block the request. For example, if you use the apollo-upload-client package with Apollo Client Web, pass headers: {'Apollo-Require-Preflight': 'true'} to createUploadLink.

New in Apollo Server 3: Apollo Server 3 does not contain a built-in integration with graphql-upload like in Apollo Server 2. Instead, the instructions below show how to integrate it yourself. You cannot do this with the "batteries-included" apollo-server library; you must use a web framework integration such as apollo-server-express instead. This page shows how to integrate graphql-upload with Express and Fastify. To implement similar functionality with another Node.js HTTP framework (e.g., Koa), see the graphql-upload documentation for more information. Some integrations might need to use graphql-upload's processRequest directly.

Integrating with Express

TypeScript
index.ts
1const express = require('express');
2const { ApolloServer, gql } = require('apollo-server-express');
3const {
4  GraphQLUpload,
5  graphqlUploadExpress, // A Koa implementation is also exported.
6} = require('graphql-upload');
7const { finished } = require('stream/promises');
8const {
9  ApolloServerPluginLandingPageLocalDefault,
10} = require('apollo-server-core');
11
12const typeDefs = gql`
13  # The implementation for this scalar is provided by the
14  # 'GraphQLUpload' export from the 'graphql-upload' package
15  # in the resolver map below.
16  scalar Upload
17
18  type File {
19    filename: String!
20    mimetype: String!
21    encoding: String!
22  }
23
24  type Query {
25    # This is only here to satisfy the requirement that at least one
26    # field be present within the 'Query' type.  This example does not
27    # demonstrate how to fetch uploads back.
28    otherFields: Boolean!
29  }
30
31  type Mutation {
32    # Multiple uploads are supported. See graphql-upload docs for details.
33    singleUpload(file: Upload!): File!
34  }
35`;
36
37const resolvers = {
38  // This maps the `Upload` scalar to the implementation provided
39  // by the `graphql-upload` package.
40  Upload: GraphQLUpload,
41
42  Mutation: {
43    singleUpload: async (parent, { file }) => {
44      const { createReadStream, filename, mimetype, encoding } = await file;
45
46      // Invoking the `createReadStream` will return a Readable Stream.
47      // See https://nodejs.org/api/stream.html#stream_readable_streams
48      const stream = createReadStream();
49
50      // This is purely for demonstration purposes and will overwrite the
51      // local-file-output.txt in the current working directory on EACH upload.
52      const out = require('fs').createWriteStream('local-file-output.txt');
53      stream.pipe(out);
54      await finished(out);
55
56      return { filename, mimetype, encoding };
57    },
58  },
59};
60
61async function startServer() {
62  const server = new ApolloServer({
63    typeDefs,
64    resolvers,
65    // Using graphql-upload without CSRF prevention is very insecure.
66    csrfPrevention: true,
67    cache: 'bounded',
68    plugins: [
69      ApolloServerPluginLandingPageLocalDefault({ embed: true }),
70    ],
71  });
72  await server.start();
73
74  const app = express();
75
76  // This middleware should be added before calling `applyMiddleware`.
77  app.use(graphqlUploadExpress());
78
79  server.applyMiddleware({ app });
80
81  await new Promise<void>(r => app.listen({ port: 4000 }, r));
82
83  console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`);
84}
85
86startServer();

Integrating with Fastify

JavaScript
1const { ApolloServer, gql } = require('apollo-server-fastify');
2const {
3  ApolloServerPluginLandingPageLocalDefault,
4} = require('apollo-server-core');
5const { GraphQLUpload, processRequest } = require('graphql-upload');
6const { finished } = require('stream/promises');
7
8const typeDefs = gql`
9  # The implementation for this scalar is provided by the
10  # 'GraphQLUpload' export from the 'graphql-upload' package
11  # in the resolver map below.
12  scalar Upload
13
14  type File {
15    filename: String!
16    mimetype: String!
17    encoding: String!
18  }
19
20  type Query {
21    # This is only here to satisfy the requirement that at least one
22    # field be present within the 'Query' type.  This example does not
23    # demonstrate how to fetch uploads back.
24    otherFields: Boolean!
25  }
26
27  type Mutation {
28    # Multiple uploads are supported. See graphql-upload docs for details.
29    singleUpload(file: Upload!): File!
30  }
31`;
32
33const resolvers = {
34  // This maps the `Upload` scalar to the implementation provided
35  // by the `graphql-upload` package.
36  Upload: GraphQLUpload,
37
38  Mutation: {
39    singleUpload: async (parent, { file }) => {
40      const { createReadStream, filename, mimetype, encoding } = await file;
41
42      // Invoking the `createReadStream` will return a Readable Stream.
43      // See https://nodejs.org/api/stream.html#stream_readable_streams
44      const stream = createReadStream();
45
46      // This is purely for demonstration purposes and will overwrite the
47      // local-file-output.txt in the current working directory on EACH upload.
48      const out = require('fs').createWriteStream('local-file-output.txt');
49      stream.pipe(out);
50      await finished(out);
51
52      return { filename, mimetype, encoding };
53    },
54  },
55};
56
57const app = require('fastify')({
58  logger: true
59});
60
61const start = async () => {
62  try {
63    // Handle all requests that have the `Content-Type` header set as multipart
64    app.addContentTypeParser('multipart', (request, payload, done) => {
65      request.isMultipart = true;
66      done();
67    });
68
69    // Format the request body to follow graphql-upload's
70    app.addHook('preValidation', async function (request, reply) {
71      if (!request.isMultipart) {
72        return;
73      }
74
75      request.body = await processRequest(request.raw, reply.raw);
76    });
77
78    const server = new ApolloServer({
79      typeDefs,
80      resolvers,
81      // Using graphql-upload without CSRF prevention is very insecure.
82      csrfPrevention: true,
83      cache: 'bounded',
84      plugins: [
85        ApolloServerPluginLandingPageLocalDefault({ embed: true }),
86      ],
87    });
88
89    // Start Apollo Server
90    await server.start();
91
92    app.register(server.createHandler());
93    await app.listen(3000);
94  } catch (err) {
95    app.log.error(err);
96    process.exit(1);
97  }
98};
99
100start();
Feedback

Edit on GitHub

Forums