gqlgen: a GraphQL server for Go that is generated from your schema
go generate based graphql server library
At a glance
- What is it?
- A Go library that reads a GraphQL schema, writes the marshalling and plumbing, and leaves you the resolver bodies. Worth understanding the generated code before you commit to it.
- Who is it for?
- gqlgen is a good fit when a Go service has to expose a schema that other teams query, because the generated surface gives you compile time errors on schema drift instead of runtime surprises. It is a poor fit if your API is small and internal, because the code generation step, the generated files and the config file are overhead a plain HTTP handler does not have.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Four commands from an empty module to a running server
The quick start in the README is short enough to read in one pass, which is the first sign that this project has thought about onboarding. You create a module, add gqlgen as a tool dependency, run an init command, and then start the generated server:
mkdir example
cd example
go mod init examplego get -tool github.com/99designs/gqlgenThat second line is the current spelling. The library is added as a Go tool dependency rather than a blank import, so the module records it in `go.mod` as tooling and it does not end up in your binary's dependency graph. Older write-ups of gqlgen tell you to `go get github.com/99designs/gqlgen` and then run it as a binary, which is a different arrangement.
The init step writes the config and the first generated files:
go tool gqlgen initAfter that the generated server is an ordinary Go program you can run directly:
go run server.goThere is no plugin registry to install and no code generation daemon to keep alive. Everything the tool needs lives in your module, which means the same command works in CI and on a colleague's laptop.
Schema first, and what the generator actually writes
gqlgen states three priorities in the README: a schema first approach where the API is defined in the GraphQL Schema Definition Language, type safety with the claim that you should never see `map[string]interface{}` in a resolver, and code generation so the repetitive parts are written for you. The type safety claim is the one that matters in practice, and it is the reason the generated code exists at all.
Instead of reflecting over your types at runtime the way a dynamic server would, gqlgen reads the schema, walks it, and writes Go source. Field selection becomes generated switch statements. Input decoding becomes generated struct unmarshalling. Response marshalling becomes generated code that writes JSON in the shape the query asked for, which is why an unused field costs nothing at runtime.
The repository layout shows how much is generated rather than written by hand. `codegen/` holds the generator, `plugin/` the extension points for models and resolvers, `graphql/` the runtime that generated code depends on, `api/` the public surface, and `_examples/` a set of runnable sample applications. `init-templates/` is what the init command writes into a fresh project, and `gqlgen.schema.json` is the schema the project dogfoods against its own tests. Generated output is meant to be committed, so a diff in review often shows both the schema change and the regenerated server in the same commit.
Controlling which fields trigger a fetch
The FAQ in the README deals with a problem every GraphQL server hits: given a recursive schema where a user has friends who have friends, how do you stop the server from loading a tree nobody asked for? The answer is a per field decision recorded in the config, not runtime convention.
The first approach is a custom model that leaves the recursive field out of the Go struct entirely:
type User struct {
ID int
Name string
}You then point the schema at that struct in `gqlgen.yml`:
# gqlgen.yml
models:
User:
model: github.com/you/pkg/model.User # go import path to the User struct aboveThe second approach keeps the generated model and marks just the one field as needing hand written logic:
# gqlgen.yml
models:
User:
fields:
friends:
resolver: true # force a resolver to be generatedEither way you end up writing the resolver yourself, and gqlgen fills in the rest of the plumbing around it:
func (r *userResolver) Friends(ctx context.Context, obj *User) ([]*User, error) {
// select * from user where friendid = obj.ID
return friends, nil
}Field resolvers run concurrently in separate goroutines, and the `worker_limit` config attribute bounds that concurrency. This is the detail worth internalising: gqlgen is fine with parallel field resolution, which means two resolvers on the same request can touch the same database at the same time, and shared state in resolvers has to be safe for that.
Config decisions that are expensive to reverse
Two config choices shape generated code in ways you only discover after you have written a lot of resolvers. The first is the backing type for the GraphQL `ID` type, which is remapped rather than accepted as a string:
models:
ID: # The GraphQL type ID is backed by
model:
- github.com/99designs/gqlgen/graphql.IntID # a go integer
- github.com/99designs/gqlgen/graphql.ID # or a go string
- github.com/99designs/gqlgen/graphql.UintID # or a go uintThe list is ordered and the first entry is the default, used when generating models from the schema and for resolver arguments. The README is blunt about the limit here: gqlgen binds automatically to the others where it can, but it has no way to know what you want in a given context. If you choose `IntID` and later want a string identifier, that is a change to every generated model and every resolver signature.
The second is `omit_getters`. Getters on generated interfaces arrived in v0.17.14 so that shared interface fields can be read without casting to a concrete type, but Relay style Connections cannot be expressed as simple getters. Setting `omit_getters: true` in `gqlgen.yml` turns them off. The smaller items are more cosmetic: the config accepts inline directives as an alternative to the YAML, with `@goModel` and `@goField` available on object and field definitions in the schema itself, including `forceResolver`, `omittable`, `batch` and `autoBindGetterHaser`.
Reading the release notes as a maintenance signal
The repository is not archived and the last push was on 2026-09-21, with v0.17.95 published on 2026-09-01. Reading what is in those releases tells you more about the cost of adopting a code generator than any marketing page would.
The v0.17.95 notes are mostly maintenance of the generated surface: a fix for batch resolver parent registration, an explicit ignore of a write result in the generated enum `MarshalGQL`, an option to omit resolver embedding, a bump to Go 1.27 in `go.mod`, and SBOM hardening. The enum `MarshalGQL` entry is the tell: generated code has to be corrected sometimes, and you end up carrying generated files that the upstream project would rather you regenerated.
The v0.17.94 notes carry an explicit warning about upcoming breaking changes, in the Federation plugin. An `@entityResolver` directive lets one resolver accept a list of representations, cutting the number of individual resolver calls, and the output slice must match the input order exactly. That warning matters to anyone planning an upgrade path: a project on the 0.17 line will see behavioural defaults change, and code that depends on the old shape will fail at compile time or, worse, at the boundary where ordering matters. The topic list on the repository includes subscriptions and dataloader, and v0.17.93 added per event context for subscription resolvers through `@subscriptionContext`, so the feature surface is still growing.
What code generation buys and what it costs
The honest comparison here is not between libraries so much as between two approaches. A reflection based Go GraphQL server reads your schema and your structs at runtime and needs no generated files, which means less to commit and less to review. What you give up is compile time checking of the schema against your types, and in a language without runtime type information to spare, that checking is a large part of why a schema first server is pleasant to maintain.
gqlgen's own framing is a feature comparison page for Go GraphQL implementations on gqlgen.com, and the README links to it rather than making the argument itself. The project is also the author of `goccy/go-yaml`, and the generated config is read with that parser rather than the standard `gopkg.in/yaml.v3`, which shows up in `go.mod`.
Where the approach genuinely costs you is in generated diffs and in the fact that a schema change is not a small change. Adding a field can touch the generated model, the generated executor, and the marshal path, all in one commit. Teams that dislike large generated diffs, or that want the freedom to change a schema without a code generation step in the build, will feel this. Teams that are already running `go generate` for protobuf or for mocks, and that have CI to check generated code is up to date, are the ones this design fits.
Editorial conclusion
gqlgen is a good fit when a Go service has to expose a schema that other teams query, because the generated surface gives you compile time errors on schema drift instead of runtime surprises. It is a poor fit if your API is small and internal, because the code generation step, the generated files and the config file are overhead a plain HTTP handler does not have. Start with the four command quick start to see the shape of the generated package, then read the config reference on gqlgen.com before you commit a schema, since most of the decisions that are expensive to reverse, such as ID remapping and forced resolvers, are made in gqlgen.yml rather than in code.
Frequently asked questions
How do I install gqlgen and generate a Go GraphQL server?
Add it as a tool dependency with `go get -tool github.com/99designs/gqlgen`, then run `go tool gqlgen init` to write the config and the first generated files. The generated server is a plain Go program, so `go run server.go` starts it.
How do I stop gqlgen from fetching fields the client did not request?
Either bind the type to a custom model that omits the field, or set `resolver: true` for that field under the type in `gqlgen.yml` so a resolver is generated. Both routes leave the resolver body to you, and field resolvers then run concurrently in separate goroutines.
Can gqlgen map the GraphQL ID type to a Go integer?
Yes. The `ID` entry in `models` takes an ordered list of backing types, and the README lists `graphql.IntID`, `graphql.ID` and `graphql.UintID`. The first entry becomes the default for generated models and for resolver arguments, so the choice is worth making before you write many resolvers.
Does gqlgen require hand written resolvers for every field?
No. Fields that map cleanly onto your generated model structs are served by generated code. You write a resolver only for fields you marked with `resolver: true`, fields bound to a custom model that lacks them, or fields whose data lives outside the struct.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/99designs-gqlgen)