Skip to main content
skill CX Codex

go-graphql — Claude Code skill: what it does, how to install

A skill for building Go GraphQL servers with gqlgen. It guides schema-first generation, fixes N+1 query fan-outs via dataloaders, and enforces query limits.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add eduardo-sl/go-agent-skills --skill go-graphql -a claude-code
Open on GitHub

What go-graphql does

The skill guides coding agents through building Go GraphQL APIs with gqlgen. It enforces a schema-first workflow where .graphql files remain the single source of truth and domain models are mapped in gqlgen.yml. Resolvers must remain thin. They hand off execution to underlying services without embedding SQL or business logic.

To eliminate N+1 query fan-outs, the skill configures batching via dataloadgen. Dataloaders are scoped per HTTP request in middleware. A process-wide loader caches across different users and causes data leaks. The skill also enforces protection against denial-of-service through FixedComplexityLimit and mandatory pagination on list fields.

Running this skill requires the Go toolchain and gqlgen installed as a module tool. It grants tool execution permissions for go, gofmt, and gqlgen. It explicitly excludes REST and gRPC workflows.

What it helps with

  • Resolve N+1 query bottlenecks in list resolvers using dataloadgen
  • Configure code generation and model bindings in gqlgen.yml
  • Enforce complexity limits and depth boundaries on incoming GraphQL queries
  • Keep resolvers thin by decoupling them from SQL and domain logic
  • Mask internal server errors using SetErrorPresenter

Example prompts for the agent

  • Set up gqlgen from schema and bind domain models in gqlgen.yml
  • Add a per-request dataloader with dataloadgen to solve N+1 queries in orderResolver
  • Configure FixedComplexityLimit and SetErrorPresenter on the GraphQL handler

How to install go-graphql

1Skills CLI (Vercel)

npx skills add eduardo-sl/go-agent-skills --skill go-graphql -a claude-code

2Manually: put the files in place

~/.claude/skills/go-graphql/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/go-graphql/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about go-graphql

How to install go-graphql in Claude Code?

Run npx skills add eduardo-sl/go-agent-skills --skill go-graphql -a claude-code. The skill file will be placed in ~/.claude/skills/go-graphql/SKILL.md.

What does go-graphql do?

It provides rules and architecture patterns for Go GraphQL servers using gqlgen. It covers schema-first generation, dataloaders for N+1 queries, query complexity limits, and error presenters.

Why must dataloaders be scoped per request?

A process-wide loader caches data across multiple client requests. Scoping loaders to individual requests via HTTP middleware prevents data leaks between users.

Source text

SKILL.md · MIT
---
name: go-graphql
description: >
  Build GraphQL servers in Go with gqlgen: schema-first generation, resolver
  structure, the N+1 problem and dataloaders, complexity and depth limits,
  error presentation, field-level authorization, and testing resolvers. Use
  when implementing or reviewing a GraphQL API, when a query fans out into
  hundreds of database calls, or when deciding what a resolver may expose.
  Trigger examples: "GraphQL", "gqlgen", "resolver", "N+1 queries",
  "dataloader", "query complexity limit", "GraphQL schema".
  Not for: REST and OpenAPI (go-openapi), gRPC (go-grpc), general HTTP
  middleware and shutdown (go-api-design).
user-invocable: true
license: MIT
compatibility: Designed for Claude Code or similar AI coding agents working on Go projects. Requires the Go toolchain. gqlgen is installed as a module tool.
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(gofmt:*) Bash(gqlgen:*)
metadata:
  author: eduardo-sl
  version: "1.0.3"
---

# Go GraphQL

GraphQL moves query planning to the client. That is the feature and the
danger: one innocuous query can become ten thousand database round trips, and
one over-permissive field can leak another tenant's data. Both are solved at
the server, not in the schema review.

Detailed reference material, loaded on demand:

- `references/errors-auth-testing.md` — error presenter, field
  authorization directive, client-driven tests with query-count asserts.

Read a reference file only when the summary below is not enough.

## 1. Schema-First with gqlgen

The `.graphql` schema is the source of truth. gqlgen generates models,
resolver stubs, and the execution layer from it.

```bash
go get -tool github.com/99designs/gqlgen
go tool gqlgen init      # once
go tool gqlgen generate  # after every schema change
```

```yaml
# gqlgen.yml — bind generated types to your own models
models:
  User:
    model: github.com/myorg/app/internal/domain.User
  ID:
    model:
      - github.com/99designs/gqlgen/graphql.ID
      - github.com/99designs/gqlgen/graphql.Int64
```

Bind domain types explicitly. Left to itself gqlgen generates a parallel set
of anaemic structs, and every resolver becomes a mapping function.

Commit generated code, and fail CI when it is stale:

```bash
go tool gqlgen generate && git diff --exit-code
```

Never edit `generated.go` or `models_gen.go`. `resolver.go` and the
`*.resolvers.go` files are yours.

## 2. Resolvers Stay Thin

A resolver translates a GraphQL request into a service call. It contains no
business logic and no SQL.

```go
func (r *queryResolver) User(ctx context.Context, id string) (*domain.User, error) {
    u, err := r.users.Find(ctx, id)
    if errors.Is(err, domain.ErrNotFound) {
        return nil, nil // nullable field: absent, not an error
    }
    if err != nil {
        return nil, fmt.Errorf("find user %s: %w", id, err)
    }
    return u, nil
}
```

Inject dependencies through the `Resolver` struct, never through package
globals:

```go
type Resolver struct {
    users  UserService
    orders OrderService
    loader *Loaders
}
```

Always propagate `ctx`. It carries the request deadline, the authenticated
principal, and the per-request dataloaders.

## 3. The N+1 Problem — the one that matters

A field resolver on a list type runs once per element.

```go
// ❌ 1 query for the orders, then N queries for the users
func (r *orderResolver) Customer(ctx context.Context, obj *domain.Order) (*domain.User, error) {
    return r.users.Find(ctx, obj.CustomerID)
}
```

Batch with a dataloader. It collects the keys requested within a short window
and issues one query.

```go
import "github.com/vikstrous/dataloadgen"

type Loaders struct {
    UserByID *dataloadgen.Loader[string, *domain.User]
}

func NewLoaders(s UserService) *Loaders {
    return &Loaders{
        UserByID: dataloadgen.NewLoader(func(ctx context.Context, ids []string) ([]*domain.User, []error) {
            return s.FindMany(ctx, ids) // ONE query for all ids
        }, dataloadgen.WithWait(time.Millisecond)),
    }
}

// ✅ 1 query for the orders, 1 for all customers
func (r *orderResolver) Customer(ctx context.Context, obj *domain.Order) (*domain.User, error) {
    return loadersFrom(ctx).UserByID.Load(ctx, obj.CustomerID)
}
```

Loaders are **per request**, installed by middleware. A process-wide loader
caches across users and leaks data between them.

```go
func withLoaders(svc UserService, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := context.WithValue(r.Context(), loadersKey{}, NewLoaders(svc))
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
```

The batch function must return results **in the order of the keys it was
given**, with a nil entry and an error per missing key. Returning a shorter
slice silently misaligns every result.

## 4. Bound Every Query

A public GraphQL endpoint without limits is a denial-of-service endpoint.

```go
srv := handler.New(generated.NewExecutableSchema(cfg))
srv.AddTransport(transport.POST{})
srv.SetQueryCache(lru.New[*ast.QueryDocument](1000))
srv.Use(extension.FixedComplexityLimit(300))
srv.Use(extension.AutomaticPersistedQuery{Cache: lru.New[string](100)})
```

- **Complexity limit** — assign a cost per field, higher for list fields with
  a large `first`. Start at a number your slowest legitimate query fits under,
  then measure.
- **Depth** — recursive types (`user { orders { customer { orders ... } } }`)
  must be bounded. gqlgen has no built-in depth limit; enforce it in an
  operation middleware.
- **Pagination is mandatory** on every list field. A field returning an
  unbounded list is a schema bug.
- **Introspection** is only enabled if you install `extension.Introspection`.
  Do not install it in production, or gate it behind an authenticated role.
- **Persisted queries** let a public client send a hash instead of a document,
  so the server executes only queries you shipped.

Set `srv.AroundOperations` to enforce a per-operation timeout, and always run
behind an `http.Server` with `ReadTimeout` and `WriteTimeout` set.

## 5. Errors

GraphQL returns 200 with an `errors` array. Never leak internals into it.

Install `srv.SetErrorPresenter`: map known domain errors (validation,
not found) to their message plus a stable `extensions.code`, and log
everything else server-side while returning `internal server error` with
code `INTERNAL`. Example: `references/errors-auth-testing.md`.

Use `srv.SetRecoverFunc` to convert a resolver panic into an error instead of
killing the connection, and log it with the stack.

Remember the nullability rule: an error on a non-null field nulls out its
nearest nullable ancestor. Make a field non-null only when it can never
legitimately be absent.

## 6. Authorization Belongs on the Field

Object-level checks are not enough — a client can reach an object through
several paths.

Declare a `@hasRole` (or similar) directive on sensitive fields and
implement it as a check against the principal in the context; return
`access denied` without detail. Example: `references/errors-auth-testing.md`.

Authenticate in HTTP middleware, before the GraphQL handler. Authorize in the
directive or the resolver, using the principal from the context — never from
a query argument.

## 7. Testing

Drive the real schema through gqlgen's test `client` and assert on the
decoded response. For every resolver behind a dataloader, also assert the
query count — that is the only way an N+1 regression fails a build rather
than a dashboard. Examples: `references/errors-auth-testing.md`.

## Verification Checklist

1. Schema is the source of truth; generated files are committed and CI-checked
2. Generated types bind to domain models via `gqlgen.yml`
3. Resolvers contain no business logic and always propagate `ctx`
4. Every list-field resolver that fetches by ID goes through a dataloader
5. Dataloaders are constructed per request, never shared across requests
6. Batch functions return one result per key, in key order
7. A complexity limit and a depth bound are configured and tested
8. Every list field is paginated
9. Introspection is disabled or role-gated in production
10. An error presenter strips internal errors; a recover func is installed
11. Authorization is enforced per field, from the context principal
12. A test asserts the query count for at least one batched field

© eduardo-sl · MIT · eduardo-sl/go-agent-skills

Similar skills

All skills for Backend and APIs →

From the same set34

go-project-layout Picks a folder layout for a new Go project: cmd/, internal/, or a flat root for small tools. Useful at the start, before the structure hardens. CX Codex +1 ★ 83repo stars go-openapi Enforces spec-first REST development in Go by generating servers and clients via oapi-codegen, validating requests, and detecting breaking API changes. CX Codex +1 ★ 83repo stars go-interface-design This skill guides Go interface design, package decoupling, and compile-time contract checks. Use it when shaping dependencies or refactoring code for testability. CX Codex +1 ★ 83repo stars go-grpc A skill that steers your coding agent through gRPC work in Go: proto contracts, error codes, deadlines, interceptors and streaming. CX Codex +1 ★ 83repo stars go-design-patterns This skill helps an agent choose Go patterns for constructors, runtime behavior and dependency failures. Use it when deciding how to structure a Go project. CX Codex +1 ★ 83repo stars go-dependency-injection A skill that steers your agent toward constructor injection and a single composition root in main. Useful once globals creep in or tests get hard to write. CX Codex +1 ★ 83repo stars go-cli Guides coding agents in building well-behaved Go CLI tools with clean signal handling, flag parsing, and Unix I/O discipline. CX Codex +1 ★ 83repo stars go-architecture-review This skill reviews Go package structure, dependency direction, and module boundaries. Use it to assess an existing codebase or plan a refactor. CX Codex +1 ★ 83repo stars

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor