go-clean-architecture
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase, Repository, Delivery layers, inward…
Use when building or reviewing a GraphQL API in Go. Covers library choice (gqlgen vs graph-gophers), schema design (nullability, pagination, mutation envelopes), thin resolver pattern, per-request DataLoaders for N+1, authentication via context plus schema directives, error
$ npx -y skills add muratmirgun/gophers --skill go-graphql --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/go-graphqlContext preview
The summary Claude sees to decide when to auto-load this skill.
Use when building or reviewing a GraphQL API in Go. Covers library choice (gqlgen vs graph-gophers), schema design (nullability, pagination, mutation envelopes), thin resolver pattern, per-request DataLoaders for N+1, authentication via context plus schema directives, error
name: go-graphql description: "Use when building or reviewing a GraphQL API in Go. Covers library choice (gqlgen vs graph-gophers), schema design (nullability, pagination, mutation envelopes), thin resolver pattern, per-request DataLoaders for N+1, authentication via context plus schema directives, error presenters, subscription lifecycle (context cancellation), and production hardening (complexity limits, introspection gating). Apply when working with github.com/99designs/gqlgen or github.com/graph-gophers/graphql-go." license: MIT compatibility: "Designed for Claude Code or similar AI coding agents. Requires Go 1.21+. gqlgen v0.17+ or graph-gophers/graphql-go v1.5+." allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)
Both production-grade Go GraphQL libraries are schema-first: write SDL (`.graphql`), bind Go resolvers. Pick the library, write the schema deliberately, and treat DataLoaders + complexity limits as non-optional.
1. **Schema is the contract.** Design nullability and pagination once; clients depend on it forever. A change from nullable to non-null is a breaking change. 2. **Resolvers are thin.** Translate GraphQL input → domain call → GraphQL output. No SQL, no business logic. 3. **DataLoaders are per-request.** Construct in HTTP middleware, stash in `context`. A package-level DataLoader is a cross-tenant data leak. 4. **Authenticate in middleware, authorize in the schema.** HTTP middleware extracts identity; schema directives (or resolver checks) enforce per-field rules. 5. **Subscriptions respect context.** Every subscription goroutine selects on `ctx.Done()` and `defer close(ch)`. Otherwise a disconnected client leaks a goroutine forever. 6. **Production limits are non-optional.** Set complexity caps; gate introspection by environment; never expose raw internal errors.
| Library | Approach | Type safety | Build step | Pick when | |---|---|---|---|---| | `github.com/99designs/gqlgen` | Codegen | Compile-time | `go generate` | Large schemas, Federation, strict types | | `github.com/graph-gophers/graphql-go` | Reflection | Parse-time | None | Small/medium schemas, simple pipeline | | `github.com/graphql-go/graphql` | Code-first | Runtime | None | **Avoid** — verbose, no SDL |
> Read [references/gqlgen.md](references/gqlgen.md) for the codegen workflow, `gqlgen.yml`, DataLoaders, and Federation. > Read [references/graph-gophers.md](references/graph-gophers.md) for the reflection model, type mapping, and tracing.
type User {
id: ID! # opaque scalar; never expose Int
email: String! # server can always return this → non-null
bio: String # may be unset → nullable
posts(first: Int = 10, after: String): PostConnection!
}
type CreateUserPayload { # mutation envelope: business errors as data
user: User
errors: [UserError!]!
}
type PostConnection { # Relay cursor pagination
edges: [PostEdge!]!
pageInfo: PageInfo!
}**Nullability rule.** A field is `!` only when the server can *always* return a value. A resolver error on a non-null field nulls the parent object — cascade failures. Nullable fields null only themselves.
**Pagination.** Cursor connections beat offset pagination on large or write-heavy datasets — cursors are stable under concurrent inserts.
**Mutation envelopes.** Wrap mutation results so business-level errors (validation, conflict) become first-class data instead of polluting the top-level `errors` array.
// Good — resolver translates and delegates.
func (r *mutationResolver) CreateUser(ctx context.Context, in CreateUserInput) (*CreateUserPayload, error) {
user, err := r.users.Create(ctx, in.Email, in.Name)
if err != nil {
return nil, presentError(err)
}
return &CreateUserPayload{User: toGQLUser(user)}, nil
}
// Bad — SQL inside the resolver.
func (r *queryResolver) User(ctx context.Context, id string) (*User, error) {
row := r.db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = $1", id)
// ...
}Use per-type resolver structs (`userResolver`, `postResolver`) instead of one monolithic resolver. It scales with the schema.
A naive `User.posts` resolver fires one SQL query per user — O(n) round-trips. DataLoaders coalesce per-field loads within a single tick into one batched query.
// Good — per-request DataLoader in middleware.
func DataLoaderMiddleware(db *sql.DB, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
loaders := &Loaders{
PostsByUser: newPostsByUserLoader(r.Context(), db),
}
ctx := context.WithValue(r.Context(), loadersKey{}, loaders)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// Bad — package-level DataLoader caches across requests.
var globalLoader = newPostsByUserLoader(context.Background(), db)Package-level DataLoaders silently serve user A's data to user B's request as long as the cached key matches. This is the most dangerous bug in Go GraphQL services.
Authenticate in HTTP middleware (extract identity, stash in `ctx`); authorize per-field via schema directives (`@hasRole(role: ADMIN)`) in gqlgen, or resolver-level checks in graph-gophers. Authorization policy belongs in the schema, not scattered across resolvers. See [references/gqlgen.md](references/gqlgen.md).
Never surface raw `error` values — they leak SQL fragments and internals. Install an `ErrorPresenter` (gqlgen) or implement `ResolverError` (graph-gophers) that returns sanitized messages. Attach a stable `code` in extensions (`NOT_FOUND`, `FORBIDDEN`) for client handling. Use `graphql.AddError(ctx, err)` for non-fatal field errors with partial data.
Every subscription goroutine must `defer close(ch)` and select on `ctx.Done()` in both the re
26 production-grade Go skills for Claude Code, Gemini CLI, and opencode. Battle-tested patterns from the Go community — codified as triggerable AI skills.
Repo: muratmirgun/gophers
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase, Repository, Delivery layers, inward…
Invoke this skill to systematically review a Go change against community style standards before merging. Walks the diff topic by topic — formatting, errors,…
Use when writing or reviewing Go code for clarity, formatting, control flow, variable declarations, switch usage, and function design. Covers the priority…
Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker pools, or fan-out/fan-in…
Use when designing, propagating, or debugging context.Context flow in Go — first-parameter placement, deadlines and cancellation, request-scoped values,…
Use when writing conditionals, loops, switches, type switches, or blank-identifier patterns in Go. Covers if-with-initialization, guard clauses, early returns,…