GraphQL Middleware
graphql-middleware is a schema wrapper designed to allow developers to compose reusable middleware functions around GraphQL resolvers. This utility enables the execution of arbitrary code both before and after a resolver is invoked, facilitating tasks such as argument modification, result transformation, logging, authentication, and error handling. The current stable version, 6.1.35, demonstrates ongoing maintenance with recent updates focused on bug fixes and dependency compatibility, particularly with newer GraphQL versions. Its release cadence is primarily driven by these maintenance needs rather than frequent new feature introductions. Key differentiators include its intuitive API, which offers complete control over the resolver lifecycle, and its broad compatibility with any standard GraphQL schema, integrating seamlessly with popular GraphQL server implementations like Apollo Server. This library promotes a clear separation of concerns, improving code structure by centralizing cross-cutting logic that would otherwise be duplicated across multiple resolvers. Developers can define middleware at various levels, from global application to specific fields, following an an "onion"-like execution principle.
Common errors
-
Error: Schema must be an instance of GraphQLSchema.
cause The `applyMiddleware` function received an argument that is not a valid GraphQLSchema object.fixEnsure that the first argument passed to `applyMiddleware` is the result of `makeExecutableSchema` or `buildSchema`, or another function that produces a `GraphQLSchema` instance. -
TypeError: Cannot read properties of undefined (reading 'Query') OR Middleware not applied.
cause This typically occurs when object-based middleware is incorrectly structured, or if middleware functions do not correctly call `await resolve(...)`.fixVerify that object-based middleware matches the schema structure (e.g., `Query`, `Mutation`, `Type.field`). For function-based middleware, always include `await resolve(root, args, context, info)` to ensure the next layer or the actual resolver is called. -
Cannot find module 'graphql-middleware' or its corresponding type declarations.
cause The package is not installed, or TypeScript cannot locate its type definitions.fixRun `npm install graphql-middleware` or `yarn add graphql-middleware`. For TypeScript, ensure `tsconfig.json` includes `node_modules/@types` in its `typeRoots` (though usually default) and that the package is correctly installed.
Warnings
- breaking As of v3.0.0, graphql-middleware no longer wraps introspection queries. This means middleware will not run for introspection operations, which might affect security or logging mechanisms that relied on this behavior.
- breaking Version 5.0.0 removed out-of-the-box support for GraphQL Yoga. If you are using GraphQL Yoga, you will need to manually integrate graphql-middleware with your Yoga schema, or consider alternatives if direct integration is critical.
- gotcha Middleware execution follows an 'onion'-like principle: the first middleware in the array is the outermost layer (executed first and last), and subsequent middlewares are inner layers. Incorrect ordering can lead to unexpected behavior.
- gotcha Modifying `args` or `context` objects within a middleware function can lead to side effects in subsequent resolvers or middleware if not managed carefully. Ensure modifications are intended and well-documented.
Install
-
npm install graphql-middleware -
yarn add graphql-middleware -
pnpm add graphql-middleware
Imports
- applyMiddleware
const { applyMiddleware } = require('graphql-middleware');import { applyMiddleware } from 'graphql-middleware'; - IMiddlewareFunction
import { IMiddlewareFunction } from 'graphql-middleware'; - IMiddleware
import { IMiddleware } from 'graphql-middleware';
Quickstart
import { ApolloServer } from 'apollo-server';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { applyMiddleware, IMiddlewareFunction, IMiddleware } from 'graphql-middleware';
const typeDefs = `
type Query {
hello(name: String): String
bye(name: String): String
}
`;
const resolvers = {
Query: {
hello: (root: any, args: { name?: string }, context: any, info: any) => {
console.log(`3. resolver: hello`);
return `Hello ${args.name ? args.name : 'world'}!`;
},
bye: (root: any, args: { name?: string }, context: any, info: any) => {
console.log(`3. resolver: bye`);
return `Bye ${args.name ? args.name : 'world'}!`;
},
},
};
// Function-based middleware for logging input and result
const logInput: IMiddlewareFunction = async (resolve, root, args, context, info) => {
console.log(`1. logInput: ${JSON.stringify(args)}`);
const result = await resolve(root, args, context, info);
console.log(`5. logInput`);
return result;
};
const logResult: IMiddlewareFunction = async (resolve, root, args, context, info) => {
console.log(`2. logResult`);
const result = await resolve(root, args, context, info);
console.log(`4. logResult: ${JSON.stringify(result)}`);
return result;
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
const schemaWithFunctionMiddleware = applyMiddleware(schema, logInput, logResult);
// Object-based middleware for modifying arguments and results on specific fields
const beepMiddleware: IMiddleware = {
Query: {
hello: async (resolve, parent, args: { name?: string }, context, info) => {
// Override arguments
const argsWithDefault = { name: 'Bob', ...args };
const result = await resolve(parent, argsWithDefault, context, info);
// Modify returned value
return (result as string).replace(/Trump/g, 'beep');
},
},
};
const finalSchema = applyMiddleware(schemaWithFunctionMiddleware, beepMiddleware);
const server = new ApolloServer({
schema: finalSchema,
});
async function startServer() {
const { url } = await server.listen({ port: 8008 });
console.log(`🚀 Server ready at ${url}`);
console.log('Test queries:');
console.log(' query { hello(name: "World") } // Expects "Hello World!" with logging');
console.log(' query { bye } // Expects "Bye world!" with logging');
console.log(' query { hello(name: "Trump") } // Expects "Hello beep!" due to middleware');
}
startServer();