gqlgen Documentation

repository·master·Indexed 27 days ago

https://github.com/99designs/gqlgen

A schema-first GraphQL server generator for Go that prioritizes type safety and uses code generation to handle API boilerplate. Features include support for batch field resolvers to prevent N+1 queries, integration with dataloaders, file upload handling via the Upload scalar, and implementation of GraphQL Subscriptions and Relay cursor-based pagination. It also provides configurations for Apollo Federation, including multi-entity resolvers and explicit requires.

Tokens
46.2K
Snippets
136
Records
190
Agent score
92%

What's inside gqlgen

  1. Compare gqlgen with other Go GraphQL implementations

    master

    When choosing a GraphQL library for Go, gqlgen is a schema-first implementation that focuses on reducing boilerplate and providing high type safety. Compared to other libraries like gophers, graphql-go, and thunder, gqlgen provides built-in support for several advanced features:

    • Code Generation: Automatically generates enums, inputs, and type bindings.
    • Advanced GraphQL Features: Full support for Mutations, Subscriptions, Federation, and Interfaces.
    • Developer Experience: Includes hooks for error logging, query complexity analysis, and native support for custom errors with error.path.
    • Performance & Scalability: Built-in support for Dataloading and high concurrency.
    • Observability: Native support for Opentracing.
  2. Enable Automatic Persisted Queries (APQ)

    master

    Automatic Persisted Queries (APQ) reduce bandwidth usage by sending only a query hash instead of the full query string. To use APQ with gqlgen, you must perform two steps:

    1. Client-side: Configure your GraphQL client to support APQ (refer to Apollo GraphQL documentation for client-specific implementation).
    2. Server-side: Implement the graphql.Cache interface and pass an instance of it to the extension.AutomaticPersistedQuery extension, which must then be applied to your GraphQL handler using .Use().
  3. Authenticate WebSocket connections using InitFunc

    master

    For WebSocket-based subscriptions, authentication typically happens during the connection initialization phase. You can use transport.WebsocketInitFunc within the AddTransport method to process the connection's initial payload.

    1. Define an InitFunc: Create a function matching the transport.WebsocketInitFunc signature. This function receives the transport.InitPayload (a map of values sent by the client).
    2. Extract Credentials: Access the payload (e.g., initPayload["authToken"]) to verify the user.
    3. Handle Failures: If authentication fails, use transport.WithWebsocketCloseCode and transport.AppendCloseReason to set a specific close code (e.g., 1008 for policy violation) and a reason before returning an error.
    4. Inject into Context: If successful, return a new context containing the user data.
    5. Register the Transport: Add the transport.Websocket to your server and provide the InitFunc.
    func webSocketInit(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) {
    	any := initPayload["authToken"]
    	token, ok := any.(string)
    	if !ok || token == "" {
    		// Set close code and reason before returning error
    		ctx = transport.WithWebsocketCloseCode(ctx, int(coderws.StatusPolicyViolation)) // 1008
    		ctx = transport.AppendCloseReason(ctx, "missing or invalid authToken")
    		return ctx, nil, errors.New("authToken not found in transport payload")
    	}
    
    	// ... verify token ...
    	userId := "john.doe"
    	ctxNew := context.WithValue(ctx, "username", userId)
    	return ctxNew, nil, nil
    }
    
    // Registering the transport
    srv.AddTransport(transport.Websocket{
    	KeepAlivePingInterval: 10 * time.Second,
    	Implementation: transport.CoderWebsocketImplementation{
    		AcceptOptions: coderws.AcceptOptions{
    			InsecureSkipVerify: true,
    		},
    	},
    	InitFunc: transport.WebsocketInitFunc(webSocketInit),
    })
  4. Verify changes with the submission checklist

    master

    Before submitting changes to gqlgen, ensure the following requirements are met:

    • Testing: All new or updated behaviors must be covered by tests. Run go test ./... and ensure concurrency-sensitive changes are tested with the -race flag.
    • Code Generation: If output changes, you must run go generate ./... in both the root directory and the _examples/ modules, and commit all regenerated files.
    • Linting: Ensure golangci-lint run returns no errors and no unexplained //nolint directives.
    • State Management: Avoid introducing new global mutable state. Dependencies must be passed explicitly rather than using package-level vars, init() side effects, or singletons.
    • Clean Diffs: Ensure the diff contains only the intended changes without unrelated reformatting.
    • Generator Integrity: Never hand-edit generated files; fixes must be implemented in templates/generator/config.
    • Breaking Changes: Any breaking changes to resolver signatures, config schemas, CLI flags, or the exported runtime API require a proposal issue and must target the next branch.
    • Documentation: All exported additions must include doc comments, and non-obvious decisions must include a comment explaining the why.
  5. Implement Entity Resolvers for Federated Servers

    master

    When building federated subgraphs, you must implement specific entity resolver methods to allow the gateway to resolve internal ID-only wrapper structs. For example, if you extend a User type with a @key(fields: "id"), you must implement a FindUserByID method in your entityResolver.

    // These two methods are required for gqlgen to resolve the internal id-only wrapper structs.
    func (r *entityResolver) FindProductByUpc(ctx context.Context, upc string) (*model.Product, error) {
    	return &model.Product{
    		Upc: upc,
    	}, nil
    }
    
    func (r *entityResolver) FindUserByID(ctx context.Context, id string) (*model.User, error) {
    	return &model.User{
    		ID: id,
    	}, nil
    }
  6. Extend generated models with extra fields via gqlgen.yml

    master

    You can add implementation-specific fields to your generated Go structs without defining them in your GraphQL schema. This is useful for passing internal data (like session information or service-specific metadata) to child resolvers. These extra fields are not exposed to GraphQL callers.

    models:
      User:
        extraFields:
          Session:
            description: "A Session used by this user"
            type: "github.com/author/mypkg.Session"
            overrideTags: 'xml:"session"'
  7. Bind GraphQL fields to embedded or anonymous structs

    master

    gqlgen supports binding fields from embedded (anonymous) Go structs. All standard binding rules (direct matching, methods, tags, and config mapping) apply to fields within embedded structs.

    This is useful for creating thin wrappers around data access types or sharing common fields across multiple models.

    Example: A Truck struct embeds a Car struct. The Truck GraphQL type can resolve fields like make or model directly from the embedded Car fields.

    type Car struct {
        Make string
        ShortState string
        LongState string
        Model string
        Color string
        OdometerReading int
    }
    
    type Truck struct {
        Car
        Is4x4 bool
    }
    type Truck {
        make: String!
        model: String!
        state: String!
        color: String!
        odometerReading: Int!
        is4x4: Bool!
    }
    models:
        Truck:
            model: github.com/my/app/models.Truck
        Car:
            model: github.com/my/app/models.Car
  8. Implement custom complexity calculation for specific fields

    master

    To account for fields that are more expensive (like those returning arrays based on an input argument), you can define custom complexity functions in your gqlgen.Config.

    A complexity function must match the signature: func(childComplexity, argValue int) int. The childComplexity represents the complexity of the child fields, and argValue is the value of the argument used to determine the cost (e.g., a count or limit argument).

    Assign these functions to the corresponding fields in c.Complexity during server setup.

    // Define a function that weights complexity by the 'count' argument
    countComplexity := func(childComplexity, count int) int {
    	return count * childComplexity
    }
    
    // Assign to the config
    c.Complexity.Query.Posts = countComplexity
    c.Complexity.Post.Related = countComplexity
    
    // Use the config to create the schema and apply the limit
    c := Config{ Resolvers: &resolvers{} }
    srv := handler.New(blog.NewExecutableSchema(c))
    srv.Use(extension.FixedComplexityLimit(5))
  9. Disable introspection based on authentication

    master

    You can control introspection on a per-request basis by using srv.AroundOperations. By accessing the graphql.GetOperationContext(ctx), you can set DisableIntrospection = true to prevent unauthorized users from querying the schema.

    srv.Use(extension.Introspection{})
    srv.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
        if !userForContext(ctx).IsAdmin {
            graphql.GetOperationContext(ctx).DisableIntrospection = true
        }
    
        return next(ctx)
    })
  10. Write tests using gqlgen conventions

    master

    When writing tests for gqlgen, follow these mechanics:

    • Assertion Library: Use github.com/stretchr/testify (require and assert). Use require for fatal failures and assert for non-fatal ones.
    • Structure: Prefer table-driven tests with t.Run subtests. This allows for named, targetable subtests.
    • Helpers: Call t.Helper() in any test helper functions so failures point to the actual test caller.
    • Scope: Test exported behavior rather than unexported internals.
    • Codegen testing: The preferred way to test codegen output is the 'regenerate-and-diff' approach used in codegen/testserver.
    • Avoid time.Sleep: Use channels, contexts, or existing synchronization for timing-related tests.
    • Parallelism: Use t.Parallel() only when tests are fully isolated and do not touch shared package-level state.
  11. Run the Type System Extension example

    master

    To run the Type System Extension example, execute the server entry point using the Go CLI. Once running, the GraphQL playground will be available at http://localhost:8080/.

    To verify the server is working, you can send a POST request to the /query endpoint with a GraphQL query for todos.

    $ go run ./server/server.go
    
    # Example query via curl
    $ curl -X POST 'http://localhost:8080/query' --data-binary '{"query":"{ todos { id text state verified } }"}'