GraphQL

Features

GraphQL extension capabilities

GraphQL Interface

The extension exposes a GraphQL interface for programmatic schema management and query execution:

type GraphQL interface {
    RegisterType(name string, obj interface{}) error
    RegisterQuery(name string, resolver FieldResolverFunc) error
    RegisterMutation(name string, resolver FieldResolverFunc) error
    RegisterSubscription(name string, resolver SubscriptionResolverFunc) error
    GenerateSchema() (string, error)
    GetSchema() *GraphQLSchema
    ExecuteQuery(ctx context.Context, query string, variables map[string]interface{}) (*Response, error)
    HTTPHandler() http.Handler
    PlaygroundHandler() http.Handler
    Use(middleware Middleware)
    EnableIntrospection(enable bool)
    Ping(ctx context.Context) error
}

Resolver Registration

Register queries, mutations, and subscriptions programmatically:

// Query resolver
server.RegisterQuery("users", func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
    limit := 10
    if l, ok := args["limit"].(int); ok {
        limit = l
    }
    return db.ListUsers(ctx, limit)
})

// Mutation resolver
server.RegisterMutation("createUser", func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
    return db.CreateUser(ctx, args["name"].(string), args["email"].(string))
})

// Subscription resolver (returns a channel)
server.RegisterSubscription("messageAdded", func(ctx context.Context, args map[string]interface{}) (<-chan interface{}, error) {
    ch := make(chan interface{})
    go func() {
        defer close(ch)
        for msg := range messageStream {
            ch <- msg
        }
    }()
    return ch, nil
})

Schema

Auto-generate a GraphQL schema from registered resolvers, or load from a schema file:

// Auto-generate
schema, _ := server.GenerateSchema()

// Or load from file
graphql.NewExtension(
    graphql.WithSchemaFile("schema.graphqls"),
)

The GraphQLSchema struct includes: Types, Queries, Mutations, Subscriptions, and Directives.

Interactive Playground

Built-in GraphQL IDE served at a configurable endpoint for exploring the API during development:

graphql.NewExtension(
    graphql.WithPlayground(true),
    graphql.WithPlaygroundEndpoint("/playground"),
)

Access the playground at http://localhost:8080/playground.

Query Protection

Complexity Limits

Reject queries that exceed a configurable complexity score (default 1000). Prevents abusive deeply nested queries:

graphql.NewExtension(graphql.WithMaxComplexity(500))

Depth Limits

Reject deeply nested queries beyond a configurable depth (default 15 levels):

graphql.NewExtension(graphql.WithMaxDepth(10))

Query Timeout

Per-request timeout enforced via context deadline (default 30 seconds):

graphql.NewExtension(graphql.WithQueryTimeout(10 * time.Second))

Query Caching

Cache parsed and validated query plans to avoid re-parsing identical queries:

  • EnableQueryCache -- enable/disable (default: true)
  • QueryCacheTTL -- cache entry TTL (default: 5 minutes)
  • MaxCacheSize -- max cached queries (default: 1000)

DataLoader Integration

Built-in N+1 query prevention. DataLoaders batch individual lookups into efficient bulk queries:

// DataLoader interface
type DataLoader interface {
    Load(key string) (interface{}, error)
    LoadMany(keys []string) ([]interface{}, []error)
    Prime(key string, value interface{})
    Clear(key string)
    ClearAll()
}

// Create a loader with batch function
loader := dataloader.NewLoader(
    dataloader.DefaultLoaderConfig(),
    func(keys []string) (map[string]interface{}, error) {
        return db.BatchGetUsers(keys)
    },
)

Configuration:

  • DataLoaderBatchSize -- max keys per batch (default: 100)
  • DataLoaderWait -- wait time to collect keys before executing batch (default: 10ms)

Executor Middleware

Chain middleware functions on the GraphQL execution pipeline:

server.Use(func(next graphql.ExecutorFunc) graphql.ExecutorFunc {
    return func(ctx context.Context, req *graphql.Request) (*graphql.Response, error) {
        start := time.Now()
        resp, err := next(ctx, req)
        log.Printf("Query took %v", time.Since(start))
        return resp, err
    }
})

Programmatic Query Execution

Execute queries from server-side code:

result, err := server.ExecuteQuery(ctx, `
    query GetUser($id: ID!) {
        user(id: $id) {
            name
            email
        }
    }
`, map[string]interface{}{"id": "123"})

File Uploads

Support multipart file uploads with configurable max size (default 10 MB).

CORS Support

Configurable allowed origins for cross-origin requests:

graphql.NewExtension(
    graphql.WithCORS(true),
    graphql.WithAllowedOrigins([]string{"https://app.example.com"}),
)

Slow Query Logging

Log queries that exceed a configurable threshold (default 1 second). Helps identify performance bottlenecks.

Introspection Control

Disable introspection for production security:

server.EnableIntrospection(false)

Observability

  • Metrics -- request count, latency histograms, error rates
  • Tracing -- distributed tracing via OpenTelemetry
  • Logging -- structured logging of queries and errors

Federation

Built-in federation support:

graphql.NewExtension(
    graphql.WithFederation(graphql.FederationConfig{Enabled: true, Version: "2.0"}),
)

Sentinel Errors

ErrorMeaning
ErrNotInitializedServer has not been initialized
ErrInvalidQueryQuery string is malformed
ErrInvalidSchemaSchema definition is invalid
ErrTypeNotFoundRequested GraphQL type not found
ErrResolverNotFoundNo resolver for the requested field
ErrExecutionFailedQuery execution failed
ErrComplexityExceededQuery complexity exceeds limit
ErrDepthExceededQuery depth exceeds limit
ErrTimeoutQuery exceeded timeout
ErrInvalidConfigConfiguration validation failed

How is this guide?

On this page