Library / SDK
maticzav/graphql-middleware avatar
maticzav/graphql-middleware

graphql-middleware: wrap a schema so every resolver gets the same before and after code

Split up your GraphQL resolvers in middleware functions

1,148 stars57 forksTypeScriptMIT

At a glance

What is it?
graphql-middleware is a schema wrapper that lets you run arbitrary code before and after any resolver, in onion order, so logging, auth and metrics live in one place instead of in every field.
Who is it for?
graphql-middleware suits a GraphQL API that has cross-cutting behaviour repeated in many resolvers, such as logging, auth checks, metrics or argument validation, and where you want that behaviour to be defined once. It is a poor fit if your needs are covered by a schema directive approach, and a poor fit for a service that wants to keep its dependency list short, because the wrapper runs a layer of indirection on every field resolution.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the wrapper actually does

GraphQL Middleware is a schema wrapper which allows you to manage additional functionality across multiple resolvers efficiently. The description is that you split up your GraphQL resolvers in middleware functions.

The mechanism is that you take an executable schema and wrap it, and every resolver in the result is replaced by a chain of middleware layers around the original function. A middleware is a resolver function that wraps another resolver function, which is the type definition the library exposes:

ts
export declare type IMiddlewareResolver<
  TSource = any,
  TContext = any,
  TArgs = any
> = (
  resolve: Function,
  parent: TSource,
  args: TArgs,
  context: TContext,
  info: GraphQLResolveInfo,
) => Promise<any>

So the signature is the familiar resolver signature with one extra first parameter, the next resolve in the chain. Everything else, parent, args, context and info, is the same argument a normal resolver receives, which is what makes the thing feel familiar rather than foreign.

The benefit is code reuse and a clear separation of concerns. A logging concern, an auth check, a metrics timer, an argument rewrite, these are the kind of things that otherwise get copy-pasted into every field resolver, and this puts them in one function that applies to the whole schema.

The library works with any GraphQL schema, and the install is a single line:

sh
yarn add graphql-middleware

The project is MIT licensed, the author is Matic Zavadlal, and the peer dependency range is wide: graphql ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0, with the runtime dependencies being @graphql-tools/delegate and @graphql-tools/schema.

The onion order, and why the array order matters

The execution model is described as following 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 array is important, and the documentation spells out why. The first resolver is the most-outer layer, so it gets executed first and last. The second is the second-outer layer, executed second and second to last, and so forth.

So the array order is the nesting order, which is the opposite of what it looks like in a list of names. If you write [authMiddleware, metricsMiddleware, beepMiddleware], auth is the outermost layer, so its before-code runs first, then metrics, then beep, then the actual resolver, and the after-code unwinds in reverse.

That has a practical consequence. If your metrics middleware times the whole call, it should be outside the auth middleware, or the timing will exclude the auth check. If a middleware short-circuits by not calling resolve, everything nested inside it is skipped. And if one middleware rewrites args, the middleware inside it sees the rewritten values, while the one outside sees the original.

The example in the documentation makes the interleaving visible with numbered log lines. The middleware logs its input first, then calls resolve, then logs after, and the resolver itself logs in between, so the sequence comes out as 1, 2, 3, 4, 5 with the resolver at position 3. Two middlewares, logInput and logResult, produce a 1, 2, 3, 4, 5 ordering where logInput is outermost.

Middleware can rewrite arguments and rewrite results

The interesting capability is that a middleware is not limited to observing. It can change what the resolver receives and what the caller gets back, and the standalone example shows both in one function.

ts
const beepMiddleware = {
  Query: {
    hello: async (resolve, parent, args, context, info) => {
      // You can use 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')
    },
  },
}

The argument override is the useful half. A middleware that injects a default, or that replaces the caller's identifier with the one from the auth context, or that strips a field the caller is not allowed to set, does it in one place instead of in every mutation that takes the same input. The default is applied with a spread that puts the injected value first, so an argument the caller did supply wins.

The result rewriting is a blunt instrument, and the example is deliberately funny about it. It is worth separating the two uses. Injecting or normalising arguments is a well-behaved middleware operation. Rewriting the returned string is the kind of thing that belongs in a test fixture rather than in production code, and a middleware that silently edits response payloads is a debugging nightmare when it goes wrong.

The shape of the object also shows the scoping mechanism. A middleware can be a bare function, which applies to every field, or an object keyed by type and field, which is what beepMiddleware is. That object form also accepts a resolve property for the fallback behaviour, along with fragment and fragments for partial matching against the query document.

Two documented removals that decide whether it fits

Two notes in the documentation change what this library can be used for, and both are stated as facts about specific versions.

The first: as of 3.0.0, graphql-middleware no longer wraps introspection queries. The consequence is that the middleware chain does not run for introspection requests, which is what a GraphQL IDE or a schema tool sends. If your auth middleware is the only thing protecting introspection, that protection is not there any more, and you need the schema-level controls instead.

The second: as of 5.0.0, graphql-middleware no longer supports GraphQL Yoga out of the box, with the note that support might come back if the library becomes maintained again, and that the docs are kept as the reference for older versions.

That second note is the one to read twice, because it tells you where the project stands. The phrasing about the library becoming maintained again, and the release list showing v6.1.35 from 2023-07-07, v6.1.34 from 2023-05-10 and v6.1.33 from 2022-10-07, say that the tags stopped moving some time ago. The repository is not archived and the last push was on 2026-10-15, so work is still happening on the branch, but the published versions are old.

There is a slight tension inside the documentation itself. A section titled usage with graphql-yoga shows the server being constructed with a middlewares array, and it says graphql-yoga has built-in support for graphql-middleware, while the version note above says support is no longer provided out of the box as of 5.0.0. The practical reading is that the built-in integration depended on the library version the Yoga release expects, and that the example in the docs is kept for older versions.

The peer dependency range accepting graphql 0.12 through 16 is the other thing to check, because it tells you the library is expected to work across a wide range of GraphQL versions, and it tells you the maintainers cared about that range rather than pinning to one release.

The ecosystem and the build

The list of middlewares built on this is the practical measure of what the library is used for, and it clusters around exactly the cross-cutting concerns the wrapper is for.

graphql-shield adds permissions as another layer of abstraction, and it is by the same author. graphql-middleware-sentry reports server errors to Sentry. graphql-middleware-apollo-upload-server handles file uploads. graphql-middleware-forward-binding is a GraphQL Binding forwardTo plugin. graphql-yup-middleware validates mutation arguments with yup, and graphql-pino-middleware augments resolvers with a pino logger. graphql-filter filters output data, and graphql-lightstep-middleware instruments resolvers with lightstep traces.

That list is a fair description of the middleware pattern's use: permissions, error reporting, validation, logging, tracing, output filtering. None of these are things you want written by hand in every resolver.

The build is a TypeScript project using bob-esbuild for compilation, with the package publishing from a dist directory. The exports map handles both module systems, with a require entry pointing at dist/index.js, an import entry at dist/index.mjs and types at dist/index.d.ts, and a wildcard subpath export for ./* for the same three. The version field reads 0.0.0-semantic-release, which is the semantic-release convention, and the release configuration pins the branch to master, with a renovate.json for dependency updates and husky for the commit hooks.

The tests run on jest with ts-jest, and the examples directory has two runnable cases, examples/logging and examples/permissions, which is a quick way to see the onion ordering and the permission pattern without reading the source.

Editorial conclusion

graphql-middleware suits a GraphQL API that has cross-cutting behaviour repeated in many resolvers, such as logging, auth checks, metrics or argument validation, and where you want that behaviour to be defined once. It is a poor fit if your needs are covered by a schema directive approach, and a poor fit for a service that wants to keep its dependency list short, because the wrapper runs a layer of indirection on every field resolution. Before adopting, check the peer dependency range, which spans graphql 0.12 through 16, and note the two documented removals, introspection queries are not wrapped as of 3.0.0 and GraphQL Yoga is not supported out of the box as of 5.0.0.

Frequently asked questions

What does graphql-middleware do?

It is a schema wrapper that lets you run arbitrary code before or after a resolver is invoked. You apply it to an executable schema and every resolver gets wrapped, which gives you code reuse and a clear separation of concerns for behaviour that would otherwise be repeated in each field.

How do I install graphql-middleware and apply it to a schema?

Run yarn add graphql-middleware, then call applyMiddleware(schema, logInput, logResult) on your executable schema and pass the result to your server. The install pulls @graphql-tools/delegate and @graphql-tools/schema, and the peer dependency accepts graphql 0.12 through 16.

In what order do graphql-middleware functions run?

They follow an onion order, and the order in the middlewares array is the nesting order. The first middleware is the most-outer layer, so it runs first and last, the second runs second and second to last, and so on, with the actual resolver in the middle.

Can a graphql-middleware function change the arguments or the result?

Yes. A middleware receives the next resolve in the chain, so it can call resolve with different args, and it can return a modified value. The documentation example injects a default name into the arguments and then rewrites the returned string.

Does graphql-middleware still support GraphQL Yoga?

The documentation notes that as of 5.0.0 it no longer supports GraphQL Yoga out of the box, that support might return if the library becomes maintained again, and that the docs are kept as a reference for older versions. It also notes that as of 3.0.0 introspection queries are no longer wrapped.

Official sources

  1. Issues
  2. License: MIT
  3. maticzav/graphql-middleware on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/maticzav-graphql-middleware.svg)](https://hysenlabs.com/projects/maticzav-graphql-middleware)

Was this useful?