Apollo Server 4 Serverless GraphQL Upload

posted on 20 March 2023 in programming

Apollo Server 4 changed the way we integrate with Serverless functions, the apollo-server-lambda project is no longer part of the core package, being replaced with @as-integrations/aws-lambda. With this change it’s no longer obvious how we can use Express middleware like graphql-upload for serverless functions, but it is still possible using the @vendia/serverless-express project. This article will focus on AWS Lambda integrations, but the solution should be just as relevant for any of the cloud providers (using the appropriate apollo-server-integrations package).

Use a signed URL upload instead

We’ll start with an obligatory statement that you probably shouldn’t be trying to upload files / images through a GraphQL mutation. The Apollo team have written up a great blog post on Apollo Server File Upload Best Practices, in summary, when possible;

  1. Use signed URL uploads to upload a file directly to a storage provider rather than through the GraphQL server, or;
  2. Use a dedicated image service. Finally, it’s not recommended but if you really want to;
  3. Upload the file with a Multipart GraphQL mutation (e.g. using graphql-upload)

With that aside, if you have a good reason for wanting to do this, then I have a solution for you.

Using graphql-upload with Apollo Server 4

In Apollo Server 3, the apollo-server-lambda package exposed an interface for using Express middleware that allowed us to use the graphql-upload package, e.g.

export const handler = server.createHandler({
  expressAppFromMiddleware(middleware) {
    const app = Express()
    return app
  expressGetMiddlewareOptions: {
    cors: {
      origin: "*",
      credentials: false,
    bodyParserConfig: { limit: "50mb" },

This has been replaced with a cleaner and leaner adapter from ` @as-integrations/aws-lambda`.

export const handler = startServerAndCreateLambdaHandler(

But how can we use graphqlUploadExpress with this?! Well it appears that Apollo Server 3 used an adapter to convert their Express app integration to a lambda handler, so plugging in Express middleware was easy. It looks like we can revert back to this method by using the @vendia/serverless-express project that exposes an Express web app API for serverless functions running on AWS Lambda or Azure Functions.

For this solution, we’ll use the Async setup Lambda handler for @vendia/serverless-express because we need to await for the Apollo Server server.start() function. We’ll also use the expressMiddleware function from Apollo Server. Here’s the full example:

const app = express()

const server = new ApolloServer({

let serverlessExpressInstance

async function setup(event, context) {
  await server.start()
    bodyParser.json({ limit: "50mb" }),
    graphqlUploadExpress({ maxFileSize: 50000000, maxFiles: 10 }),
    expressMiddleware(server, {
      context: async () => { /* Your context function */ },
  serverlessExpressInstance = serverlessExpress({ app })
  return serverlessExpressInstance(event, context)

exports.handler = (event, context) => {
  if (serverlessExpressInstance)
    return serverlessExpressInstance(event, context)

  return setup(event, context)

Preventing CSRF errors

The Apollo team have taken care to ensure that Apollo Server is secure by default, and this includes disabling “simple” HTTP operations that don’t require a CORS preflight check and may be vulnerable to CSRF attacks. You can read about Preventing Cross-Site Request Forgery (CSRF) here.

What this means is that when using the graphql-upload package you’re likely to run into the following error:

  "errors": [
      "message": "This operation has been blocked as a potential Cross-Site Request Forgery (CSRF). Please either specify a 'content-type' header (with a type that is not one of application/x-www-form-urlencoded, multipart/form-data, text/plain) or provide a non-empty value for one of the following headers: x-apollo-operation-name, apollo-require-preflight\n",
      "extensions": {
        "code": "BAD_REQUEST",

The message is pretty self-explanatory, but essentially to enable uploads you will need to either;

  1. Send an Apollo-Require-Preflight or X-Apollo-Operation-Name HTTP header with the request, or;
  2. Disable CSRF protection with new ApolloServer({ csrfPrevention: false }) (but this is a bad idea so don’t do this)

The Apollo docs callout how to do this with the apollo-upload-client package:

For example, if you use the apollo-upload-client package with Apollo Client Web, pass {headers: {'Apollo-Require-Preflight': 'true'}} to createUploadLink.