All in one solution to manage middleware in your GraphQL projects.
GraphQL Middleware is a schema wrapper which allows you to manage additional functionality across multiple resolvers efficiently.
- Easiest way to handle GraphQL middleware: An intuitive, yet familiar API that you will pick up in a second.
- Powerful: Allows complete control over your resolvers (Before, After).
- Compatible: Works with any GraphQL Schema.
- Remote: Accepts
fragments
in resolvers to connect with remote schemas.
yarn add graphql-middleware
import { applyMiddleware } from 'graphql-middleware'
import { makeExecutableSchema } from 'graphql-tools'
import { authMiddleware, metricsMiddleware } from './middleware'
// Minimal example middleware (before & after)
const beepMiddleware = {
Query: {
hello: async (resolve, parent, args, context, info) => {
// You can you middleware to override arguments
const argsWithDefault = { name: 'Bob', ...args }
const result = await resolve(parent, argsWithDefault, context, info)
// Or change the returned values of resolvers
return result.replace(/Trump/g, 'beep')
},
},
}
const typeDefs = `
type Query {
hello(name: String): String
}
`
const resolvers = {
Query: {
hello: (parent, { name }, context) => `Hello ${name ? name : 'world'}!`,
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
const schemaWithMiddleware = applyMiddleware(
schema,
metricsMiddleware,
authMiddleware,
beepMiddleware,
)
graphql-yoga
has built-in support forgraphql-middleware
!
import { GraphQLServer } from 'graphql-yoga'
import { authMiddleware, metricsMiddleware } from './middleware'
const typeDefs = `
type Query {
hello(name: String): String
}
`
const resolvers = {
Query: {
hello: (parent, { name }, context) => `Hello ${name ? name : 'world'}!`,
},
}
const server = new GraphQLServer({
typeDefs,
resolvers,
middlewares: [authMiddleware, metricsMiddleware],
documentMiddleware: [],
})
server.start(() => console.log('Server is running on localhost:4000'))
- graphql-middleware-apollo-upload-server - Uploading files is hard, that's why this package manages it for you!
- graphql-shield - Permissions as another layer of abstraction.
- graphql-middleware-sentry - Report your server errors to Sentry.
- graphql-middleware-forward-binding - GraphQL Binding forwardTo plugin for GraphQL Middleware.
A middleware is a resolver function that wraps another resolver function.
export declare type IMiddlewareResolver<
TSource = any,
TContext = any,
TArgs = any
> = (
resolve: Function,
parent: TSource,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo,
) => Promise<any>
export interface IMiddlewareWithFragment<
TSource = any,
TContext = any,
TArgs = any
> {
fragment: string
resolve?: IMiddlewareResolver<TSource, TContext, TArgs>
}
export type IMiddlewareFunction<TSource = any, TContext = any, TArgs = any> =
| IMiddlewareWithFragment<TSource, TContext, TArgs>
| IMiddlewareResolver<TSource, TContext, TArgs>
interface IMiddlewareTypeMap {
[key: string]: IMiddlewareFunction | IMiddlewareFieldMap
}
interface IMiddlewareFieldMap {
[key: string]: IMiddlewareFunction
}
type IMiddleware = IMiddlewareFunction | IMiddlewareTypeMap
function middleware(
generator: (schema: GraphQLSchema) => IMiddleware,
): MiddlewareGenerator
function applyMiddleware(
schema: GraphQLSchema,
...middleware: (IMiddleware | MiddlewareGenerator)[]
): GraphQLSchema & {
schema?: GraphQLSchema
fragmentReplacements?: FragmentReplacement[]
}
/**
* Applies middleware to a schema like `applyMiddleware` but only applies the
* middleware to fields that have non-default resolvers. This method can be
* useful if you want to report performance of only non-trivial methods.
*/
function applyMiddlewareToDeclaredResolvers(
schema: GraphQLSchema,
...middleware: (IMiddleware | MiddlewareGenerator)[]
): GraphQLSchema & {
schema?: GraphQLSchema
fragmentReplacements?: FragmentReplacement[]
}
In some cases, your middleware could depend on how your schema looks. In such situations, you can turn your middleware into a middleware generator. Middleware generators are denoted with function middleware
and receive schema
as the first argument.
const schemaDependentMiddleware = middleware(schema => {
return generateMiddlewareFromSchema(schema)
})
const schemaWithMiddleware = applyMiddleware(
schema,
schemaDependentMiddleware,
someOtherOptionalMiddleware,
etc,
)
Fragments are a way of expressing what information your resolver requires to make sure it can execute correctly. They are primarily used in schema forwarding when the client might not always request all the fields your resolver demands. Because of that, we need to provide a way of telling what other information we need from a remote schema and that's why we use fragments.
You can read more about fragments in the graphql-binding
repository and on graphql-tools
documentation website.
GraphQL Middleware provides a convenient way to quickly and easily add fragments to your middleware. This might turn out particularly useful when your middleware depends on resolver data.
We've made fragments extremely flexible by using the general API which, if you have ever run over fragments, you probably already know.
// Schema wide - gets applied to every field.
const middlewareWithFragments = {
fragment: `fragment NodeID on Node { id }`,
resolve: (resolve, { id }, args, ctx, info) => {
const foo = doSomethingWithID(id)
return resolve(foo)
},
}
// Type wide - gets applied to every field of certain type.
const middlewareWithFragments = {
Query: {
fragment: `fragment NodeID on Node { id }`,
resolve: (resolve, { id }, args, ctx, info) => {
const foo = doSomethingWithID(id)
return resolve(foo)
},
},
Mutation: (resolve, parent, args, ctx, info) => {
return resolve(parent, customArgs)
},
}
// Field scoped - gets applied to particular field.
const middlewareWithFragments = {
Query: {
node: {
fragment: `fragment NodeID on Node { id }`,
resolve: (resolve, { id }, args, ctx, info) => {
const foo = doSomethingWithID(id)
return resolve(foo)
},
},
books: (resolve, parent, args, ctx, info) => {
return resolve(parent, customArgs)
},
},
}
const { schema, fragmentReplacements } = applyMiddleware(
schema,
middlewareWithFragments,
someOtherMiddleware,
)
graphql-middleware
automatically merges fragments from multiple middlewares if possible. Otherwise, validation function throws an error.
- Logging
- Metrics
- Input sanitisation
- Performance measurement
- Authorization
- Caching
- Tracing
Yes. Nevertheless, we encourage you to use it in combination with Yoga. Combining the power of middlewares
that GraphQL Middleware offers, with documentMiddleware
which Yoga exposes, gives you unparalleled control over the execution of your queries.
GraphQL Middleware and directives
tackle the same problem in a completely different way. GraphQL Middleware allows you to implement all your middleware logic in your code, whereas directives encourage you to mix schema with your functionality.
GraphQL Middleware allows you to modify the context of your resolvers, but we encourage you to use GraphQL Yoga's documentMiddleware
for this functionality instead.
Join our Slack community if you run into issues or have questions. We love talking to you!