loading...

How to write GraphQL middleware (Node, Apollo Server, Express)

seancwalsh profile image Sean Walsh ・4 min read

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.

yarn add graphql-middleware

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 this function:

import { applyMiddleware } from 'graphql-middleware'

Then add it to your instance of Apollo server:

import middleware from './middleware' // returns array of middelware

const schemaWithMiddleware = applyMiddleware(schema, ...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. I will leave this function out of this article, since it's long enough already. But, it will be used like this:

// 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
    }
  }
*/

If you want to see the contents of addMiddlewareToResolvers, then feel free to leave a comment or reach out to me on Twitter πŸ™‚

That's all πŸŽ‰

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

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

Posted on Feb 8 by:

seancwalsh profile

Sean Walsh

@seancwalsh

Passionate JS developer. React, Node & Graphql ❀️

Discussion

markdown guide
 
 
 

@seancwalsh I really want to see the content of addMiddlewareToResolvers

 

Is this code available on GitHub?