$npx -y skills add samber/cc-skills-golang --skill golang-graphqlImplements GraphQL APIs in Golang using gqlgen or graphql-go. Apply when building GraphQL servers, designing schemas, writing resolvers, handling subscriptions, or integrating GraphQL with existing Go HTTP services. Also apply when the codebase imports `github.com/99designs/gqlge
| 1 | **Persona:** You are a Go GraphQL engineer. You design schemas deliberately, batch database access to prevent N+1, and treat query complexity limits as non-optional in production. |
| 2 | |
| 3 | **Modes:** |
| 4 | |
| 5 | - **Build mode** — generating new schemas, resolvers, or server setup: follow the skill's sequential instructions; launch a background agent to grep for existing resolver patterns and naming conventions before generating new code. |
| 6 | - **Review mode** — auditing a GraphQL codebase or PR: use a sub-agent to scan for N+1 resolver patterns, missing complexity caps, global DataLoaders, and introspection enabled in production, in parallel with reading the business logic. |
| 7 | |
| 8 | > **Community default.** A company skill that explicitly supersedes `samber/cc-skills-golang@golang-graphql` skill takes precedence. |
| 9 | |
| 10 | # Go GraphQL Best Practices |
| 11 | |
| 12 | Both major libraries are schema-first: write SDL (`.graphql` files), bind Go resolvers. Choose based on project size and team preferences. |
| 13 | |
| 14 | This skill is not exhaustive. Refer to each library's official documentation and code examples for current API signatures. For Go package docs, symbols, versions, importers, and known vulnerabilities, → See `samber/cc-skills-golang@golang-pkg-go-dev` skill (`godig`) — prefer it over Context7 for Go package facts. To navigate this library's usage in your own code (definitions, call sites, diagnostics), → See `samber/cc-skills-golang@golang-gopls` skill (`gopls`). Context7 remains a fallback for docs not indexed on pkg.go.dev. |
| 15 | |
| 16 | ## Library Choice |
| 17 | |
| 18 | | Library | Approach | Type safety | Build step | Best for | |
| 19 | | --- | --- | --- | --- | --- | |
| 20 | | `github.com/99designs/gqlgen` | Codegen | Compile-time | `go generate` | Large schemas, federation, strict types | |
| 21 | | `github.com/graph-gophers/graphql-go` | Reflection | Parse-time | None | Simple schemas, fast iteration | |
| 22 | | `github.com/graphql-go/graphql` | Code-first | Runtime | None | **Avoid** — verbose, no SDL | |
| 23 | |
| 24 | Pick **gqlgen** when: Apollo Federation is required, schema is large (100+ types), or the team wants generated stubs and zero reflection overhead. |
| 25 | |
| 26 | Pick **graph-gophers** when: schema is small/medium, the build pipeline should stay simple, or a dynamic schema is needed. |
| 27 | |
| 28 | For deep-dive on each library, see [gqlgen reference](./references/gqlgen.md) and [graphql-go reference](./references/graphql-go.md). |
| 29 | |
| 30 | ## Schema Design |
| 31 | |
| 32 | ```graphql |
| 33 | # ✓ Good — explicit nullability; ID scalar for opaque identifiers |
| 34 | type User { |
| 35 | id: ID! |
| 36 | email: String! # non-null: the server can always return this |
| 37 | bio: String # nullable: may be unset |
| 38 | posts(first: Int = 10, after: String): PostConnection! |
| 39 | } |
| 40 | |
| 41 | # ✗ Bad — Int ID leaks implementation details, breaks client caching |
| 42 | type Post { |
| 43 | id: Int! |
| 44 | } |
| 45 | ``` |
| 46 | |
| 47 | **Nullability rule:** mark a field `!` only when the server can _always_ return a value. A resolver error on a non-null field nulls the parent object, causing cascade failures; nullable fields only null the field itself. |
| 48 | |
| 49 | **Pagination:** use Relay cursor connections (`Connection`/`Edge`/`PageInfo`) for list fields. Avoid offset pagination on large datasets — cursors are stable under concurrent writes. |
| 50 | |
| 51 | **Mutations:** wrap results in an envelope type so clients receive business errors alongside partial results without polluting the GraphQL `errors` array: |
| 52 | |
| 53 | ```graphql |
| 54 | type CreateUserPayload { |
| 55 | user: User |
| 56 | errors: [UserError!]! |
| 57 | } |
| 58 | ``` |
| 59 | |
| 60 | ## Resolver Patterns |
| 61 | |
| 62 | Keep resolvers thin — they translate GraphQL inputs to domain calls and domain responses to GraphQL outputs. |
| 63 | |
| 64 | ```go |
| 65 | // ✓ Good — resolver delegates to service layer |
| 66 | func (r *mutationResolver) CreateUser(ctx context.Context, input model.CreateUserInput) (*model.CreateUserPayload, error) { |
| 67 | user, err := r.userService.Create(ctx, input.Email, input.Name) |
| 68 | if err != nil { |
| 69 | return nil, formatError(err) |
| 70 | } |
| 71 | return &model.CreateUserPayload{User: toGQLUser(user)}, nil |
| 72 | } |
| 73 | |
| 74 | // ✗ Bad — SQL in resolver, no separation of concerns |
| 75 | func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error) { |
| 76 | row := r.db.QueryRowContext(ctx, "SELECT * F |