master
master v0.17.90 v0.17.89 v0.17.88 v0.17.87 v0.17.86 v0.17.85 v0.17.84 v0.17.83 v0.17.82 v0.17.81 v0.17.80 v0.17.79 v0.17.78 v0.17.77 v0.17.76 v0.17.75 v0.17.74 v0.17.73 v0.17.72 v0.17.71

Middlewares

Middlewares and Interceptors
[edit]
You are looking at the docs for the unreleased master branch. The latest version is v0.17.90.

Overview

gqlgen provides a robust set of hooks to intercept and modify the GraphQL execution lifecycle. These are commonly referred to as middlewares or interceptors. They are particularly useful for cross-cutting concerns like:

You can register middlewares directly on your handler.Server instance.

Field Middleware (AroundFields)

Field middleware runs for every field in a GraphQL query that is resolved. It is highly granular and is the perfect place to enforce field-level permissions or log resolver execution times.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/99designs/gqlgen/graphql"
	"github.com/99designs/gqlgen/graphql/handler"
)

func main() {
	srv := handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: &Resolver{}}))

	srv.AroundFields(func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
		rc := graphql.GetFieldContext(ctx)

		// Example: Logging field execution time
		start := time.Now()
		res, err = next(ctx)

		fmt.Printf("Field %s.%s took %v\n", rc.Object, rc.Field.Name, time.Since(start))

		return res, err
	})

	// ... continue server setup
}

Operation Middleware (AroundOperations)

Operation middleware runs once for the entire GraphQL operation (Query, Mutation, or Subscription). It is commonly used for request-level validation, authenticating the user before any resolvers run, or logging the entire query payload.

srv.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
	oc := graphql.GetOperationContext(ctx)
	fmt.Printf("Incoming operation: %s\n", oc.OperationName)

	// Example: Reject unauthenticated requests
	if !isAuthorized(ctx) {
		// Use graphql.OneShot to ensure the error is sent exactly once!
		return graphql.OneShot(graphql.ErrorResponse(ctx, "unauthorized"))
	}

	// Continue executing the operation
	return next(ctx)
})

Short-circuiting Requests (Important!)

If you want to reject a request inside an operation middleware (e.g., the user is not authenticated), you might be tempted to simply return an error. However, GraphQL handles streaming responses (like Subscriptions over WebSockets) where the transport iterates over responses until it receives nil.

If you return an error without executing next(), you must wrap it in graphql.OneShot. Failing to do so will cause streaming transports to loop infinitely and spam the client with the same error!

Root Field Middleware (AroundRootFields)

Root field middleware is similar to field middleware, but it only runs for the root fields defined on your Query, Mutation, or Subscription types. This is useful if you want to apply logic specifically at the entry points of your graph without incurring the performance overhead of running it on every single nested field.

srv.AroundRootFields(func(ctx context.Context, next graphql.RootResolver) graphql.Marshaler {
	rc := graphql.GetRootFieldContext(ctx)
	fmt.Printf("Executing root field: %s\n", rc.Field.Name)

	return next(ctx)
})

Response Middleware (AroundResponses)

Response middleware hooks into the very end of the execution lifecycle, just before the response is serialized and sent back to the client. This can be run multiple times per operation if the operation is a Subscription.

srv.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
	// Execute the operation and get the response
	resp := next(ctx)

	if resp != nil && len(resp.Errors) > 0 {
		fmt.Printf("Operation finished with %d errors\n", len(resp.Errors))
	}

	return resp
})

Advanced: Handler Extensions

For more complex plugins, gqlgen provides the graphql.HandlerExtension interface. Extensions can hook into multiple parts of the lifecycle at once (parameter mutation, context mutation, operations, and fields).

Built-in features like Automatic Persisted Queries (APQ) and Apollo Tracing are implemented as handler extensions. You can register an extension using srv.Use():

srv.Use(extension.FixedComplexityLimit(50))

To create your own, implement graphql.HandlerExtension and the specific interceptor interfaces (e.g., graphql.OperationInterceptor, graphql.FieldInterceptor) you need.