How to write GraphQL middleware (Node, Apollo Server, Express)
Tags: graphql, apollo, node, middleware
Published: 2020-02-08 08:50:23

In this article, we will use `Node.js` `apollo-server-express` with the `graphql-middleware` package.

I will assume that you are familiar with Node.js, Apollo server, Express, and ES6+ syntax.

I will skip most of the setup and assume you already have a GraphQL API set up with Apollo server. So let's install `graphql-middleware` and `graphql-tools`.

```
yarn add graphql-middleware graphql-tools
// or 
npm install graphql-middleware graphql-tools
```

Then, create a middleware folder with index file. You can, of course, structure this however you like.

```
mkdir src/middleware && touch src/middleware/index.js
```

Now, we have to add the middleware to the Apollo server constructor. So, navigate to your server.js file (or wherever you create your instance of Apollo).

First, import these functions:
```
import { applyMiddleware } from 'graphql-middleware';
import { makeExecutableSchema } from 'graphql-tools';
```

Then add it to your instance of Apollo server:
```
import resolvers from './resolvers' // returns array of resolvers
import middleware from './middleware' // returns array of middelware

// this combines all of the resolvers
const executableSchema = makeExecutableSchema({ typeDefs: schema, resolvers });
const schemaWithMiddleware = applyMiddleware(executableSchema, ...middleware);

const server = new ApolloServer({
	playground: true,
	typeDefs: schema,
	resolvers,
	context: async ({ req, res }) => ({ req, res }), // now we can access express objects from apollo context arg 
	schema: schemaWithMiddleware, // add this property
});
```

Okay, the setup is complete, now we are ready to write some middleware. In this example, we will create some middleware which will check the incoming request to the server includes a valid session cookie for user authentication.

Let's create a file in the middleware folder:

`touch src/middleware/getUserFromCookie.js`

Now, before we forget, let's import this file to middleware/index.js file:

```
import getUserFromCookie from './getUserFromCookie';
    
export default [getUserFromCookie];
```

Let's make a plan for this module. I often like to write a brief plan in comments:
```
// TODO
// 1. get session cookie from express request object
// 2. use session id to get user details
// 3. add user to Apollo args
// 4. specify which resolvers to add the middleware to
```

Now we're ready. Let's start with the number 1:

```
async function getUserFromCookie(req) {
  try {
    const { clientSession } = req.cookies; // requires cookie-parser middleware

    if (!clientSession) {
      throw new Error('session cookie does not exist');
    }

    return await getUser(clientSession); // get user details from Database
  } catch (error) {
    throw new AuthenticationError(`Cannot get user from cookie: \n ${error}`);
  }
}
```

What's going on here? Where does the `req` param come from!? Bear with me. We will call this function later and pass this argument.

To easily get access to your cookies, like we get here in this function, you will need to install the `cookie-parser` middleware package. I will leave this out of this article.

If this middlware cannot find any middleware, then we should block the client from getting any access to the api. We can use Apollo servers very helpful collection of predefined errors.

We will skip the `getUser` function in this article, since this is specific to how get user data in your api.

So, that covers 1. and 2. from our TODOs, let's move on to 3. Add user details to Apollo args. This should allow us to access the user details in the specified resolvers.

```
async function addUserToArgs(resolve, parent, args, context, info) {
  const user = await getUserFromCookie(context.req);
  const argsWithUser = { user, ...args };
    
  return resolve(parent, argsWithUser, context, info);
}
```

This is the middleware function. Some points to note:

- The four arguments being passed in to this function will be passed to all middleware.
- Any code that comes *before* `resolve` will run before the resolver is executed
- Any code *after* the `resolve` function will run after the resolver is executed
- You can choose what arguments to pass to your resolver. In this case, we have added the user object to args, so the resolver can access `args.user`

At this point, you're probably wondering how to choose which resolvers use this middleware. This brings us to point number 4 from our TODOs.

We have to export an object which includes the resolver names as keys, and the middleware function as values. The graphql-middleware package will then work some magic to ensure this function is run on the specified resolvers.

```
export default {
  Query: {
    getUserDetails: addUserToArgs,
  },
  Mutation: {
    updateUserDetails: addUserToArgs,
  },
};
```

Okay, we're almost done! But, you may be wondering at this point, what if I want to add some middleware to all resolvers (or a lot of resolvers), then this will quickly become tedious and very difficult to maintain as the api grows. 

For this reason, I wrote a helper function which accepts as arguments an array of resolvers, and the middleware function. This will use the array reduce method to return one object with the resolver as the key and the middleware as the value.  Here's how to use the helper function:
```
// import array of objects with Query and Mutaion properties
import resolvers from '../../resolvers';
import addMiddlewareToResolvers from './addMiddlewareToResolvers';
    
// pass array of resolvers and middleware function
export default addMiddlewareToResolvers(resolvers, addUserToArgs);
    
/*
  return {
    Query: {
      getUserDetails: addUserToArgs
      // rest of the queries
    },
    Mutation: {
      updateUserDetails: addUserToArgs
      // rest of the mutations
    }
  }
*/
```

And here's the function(s). It's a little complex, if anyone can simplify this and make it more readable I'd love to see it!
```
import { ApolloError } from 'apollo-server-express'

// returns object with resolver names as keys, and middleware function as value
export default function addMiddleware(
  resolvers,
  middlewareFunction,
) {
  try {
    return resolvers?.reduce(
      (a, c) => buildResolverObject(a, c, middlewareFunction),
      {},
    )
  } catch (error) {
    throw new ApolloError(`Error in addMiddlewareToResolvers - ${error}`)
  }
}

function buildResolverObject(
  accumulator: any,
  { Query, Mutation },
  middlewareFunction: any,
) {
  const queryProperties = getResolverProperties(Query, middlewareFunction)
  const mutationProperties = getResolverProperties(Mutation, middlewareFunction)

  return {
    Query: {
      ...accumulator.Query,
      ...queryProperties,
    },
    Mutation: {
      ...accumulator.Mutation,
      ...mutationProperties,
    },
  }
}

function getResolverProperties(resolverObject = {}, middlewareFunction) {
  const keys = Object.keys(resolverObject)
  const properties = keys.map((key) => ({ [key]: middlewareFunction }))

  return properties.reduce((a, c) => ({ ...a, ...c }), {})
}
```

That's all  🎉

Now you are ready to write your own custom middleware. Have fun!

P.S. Interested how to write integration tests using Jest for this middleware? Coming soon 😎