Middlewares
Middleware is a way to intercept individual resolve functions or a group of them and return a different result or throw an exception based on a condition. Each middleware can access everything that resolves function receives. The ability to access context and the Injector
makes it even more powerful.
Middleware function
The middleware function can be sync
or async
and accepts two arguments. The first one is an object containing root
, args
, context
and info
properties, we call it event
. The other argument is a function that calls the next middleware, called next
.
What is the event
object?
Take a look at the regular resolve function.
function resolve(root, args, context, info) {
// ...
}
It accepts four arguments and in Middlewares we simply wrap them in an object.
function middleware({ root, args, context, info }, next) {
// ...
}
Because a middleware function can access context
, it can also extract the Injector
and get access to Dependency Injection.
How to use next
function?
The second argument of middleware is the next
function and its only job is to call the middleware or the resolve function itself.
function middleware({ root, args, context, info }, next) {
// code
return next()
}
It’s important to understand that every middleware should do one of three things:
- throw an exception
- return the result of
next()
- return a value
In the case where a middleware returns nothing (undefined
), it’s going to be used as the result of a field resolver. So be careful!
Intercepting results
Middlewares are capable of intercepting the field resolver or even not invoking it at all and resolving a different value.
An example of how to let the field resolver run and intercept its result:
async function middleware({ root, args, context, info }, next) {
// code
const result = await next()
if (someCondition(result)) {
return null
}
return result
}
You can also resolve any value, without calling the field resolve function:
async function middleware({ root, args, context, info }, next) {
// code
return {
// my result
}
}
Exceptions
Middlewares behave like regular resolve functions, meaning they can also throw exceptions. It’s a useful thing when you need to make sure a field can only be access when the user is logged in or has valid rights.
async function middleware({ root, args, context, info }, next) {
if (!context.injector.get(Auth).isLoggedIn()) {
throw new Error('Not logged in')
}
return next()
}
Registering Middlewares
You know how to write middlewares and what they offer, now let’s match a middleware with a corresponding type and field.
Three ways of registering middlewares:
- A specific field of a specific type
- All fields of a specific type
- All fields of all types
Here’s a first option, intercepting the Query.me
field resolver:
{
"Query": {
"me": [yourMiddleware]
}
}
To intercept all fields of a specific type use *
mask:
{
"Query": {
"*": [yourMiddleware]
}
}
To intercept all fields of all possible types, use *
mask twice:
{
"*": {
"*": [yourMiddleware]
}
}
Now let’s understand how to register middlewares in a Module and an Application. Take a look at the following example:
import { createModule } from 'graphql-modules';
const myModule = createModule({
// ...
middlewares: {
Query: {
me: [myMiddleware]
}
}
})
Execution order
Without strict rules on the order of execution, you might get unexpected results.
-> Application *.*
-> Module *.*
-> Application Type.*
-> Module Type.*
-> Application Type.Field
-> Module Type.Field