August 07, 2018
Open Sourcing GraphQL Middleware - Library to Simplify Your Resolvers
GraphQL Middleware lets you run arbitrary code before or after a resolver is invoked. It improves your code structure by enabling code reuse and a clear separation of concerns.
Middleware keeps resolvers clean
A well-organized codebase is key for the ability to maintain and easily introduce changes into an app. Figuring out the right structure for your code remains a continuous challenge - especially as an application grows and more developers are joining a project.
A common problem in GraphQL servers is that resolvers often get cluttered with business logic, making the entire resolver system harder to understand and maintain.
GraphQL Middleware uses the middleware pattern (well-known from Express.js) to pull out repetitive code from resolvers and execute it before or after one your resolvers is invoked. This improves code modularity and keeps your resolvers clean and simple.
Understanding middleware functions
When using GraphQL Middleware, you're removing functionality from your resolvers and put it into dedicated middleware functions. These functions effectively wrap a resolver function, meaning they ...
- ... have access to the same resolver input arguments.
- ... decide what the resolver ultimately returns.
- ... can catch and throws errors in the resolver chain.
A simple example
Here is how you would implement a simple example of a logging middleware that prints the input arguments and return value of a resolver:
Diving deeper
Applying middleware to all resolvers
Let's take a look at another example where we're using two middleware functions to log the query arguments and the returned result of all resolvers in our schema. The numbers at the beginning of each console.log
statement indicate the execution order:
โถ Run Example
Understanding the middleware execution flow
Assume the GraphQL server receives the following query:
Here is what will be printed to the console:
Execution of the middleware and resolver functions follow the "onion"-principle, meaning each middleware function adds a layer before and after the actual resolver invocation.
The order of the middleware functions in the middlewares
array is important. The first resolver is the "most-outer" layer, so it gets executed first and last. The second resolver is the "second-outer" layer, so it gets executed second and second to last... And so forth.
If the two functions in the array were switched, the following would be printed:
Applying middleware to specific resolvers
Rather than applying your middlewares to your entire schema, you can also apply them to specific resolvers (on a field- as well as on a type-level). For example, to apply only the logInput
to the Query.hello
resolver and both middlewares to the Query.bye
resolver, you can use the following syntax:
Processing the same hello
query from above, this would produce the following console output:
Here is an illustration of the execution flow:
Input arguments of middleware functions
The logInput
and logResult
functions receive five input arguments each:
- The first one is the resolver function to which the middleware is applied.
- The remaining four represent the standard resolver arguments (learn more here).
Inside of the middleware function, you need to manually invoke the resolver at some point. Notice that you also need to actually return the resolver's result from the middleware function (this also lets you transform the return value of a resolver).
GraphQL Middleware vs Schema directives
Using GraphQL schema directives is another option to add functionality to your resolver system. The biggest differences between GraphQL Middleware and schema directives are twofold:
- GraphQL Middleware is imperative while schema directives are declarative.
- Middleware functions are more flexible since they can be applied to specific fields, types and/or the entire schema (meaning to all your resolvers at once) while schema directives can be applied only to specific fields and/or types.
Schema directives require you to annotate your SDL schema definition with special directives to add additional behaviour to your resolver system. If you prefer having your schema definition free from business logic and be only responsible for defining API operations and modeling data, GraphQL Middleware is the right tool for you.
Getting started with graphql-middleware
The graphql-middleware
library can be installed via NPM:
When using with graphql-yoga
, the middleware functions can be passed directly into the GraphQLServer
constructor. Other servers require you to create an executable schema first and then apply the middleware functions to it (using the applyMiddleware
function as shown in the first example).
Made by the awesome GraphQL community
At Prisma, we deeply care about the GraphQL ecosystem and are especially excited about this project as it was driven primarily by our awesome community. Most notably by Matic Zavadlal who did an amazing job as the core maintainer of the project! ๐
๐ Star on GitHub ๐
Matic also already built several libraries on top of graphql-middleware
that you might find useful for your GraphQL server development:
graphql-middleware-apollo-upload-server
: Manage file uploads.graphql-shield
: Easily implement permission rules in your resolvers.graphql-middleware-sentry
: Reports errors to Sentry.
We're excited to see what you're going to build with GraphQL Middleware!
Donโt miss the next post!
Sign up for the Prisma Newsletter