104 The gqlgen File Structure and How It Works
104 The gqlgen File Structure and How It Works
If you have ever built a GraphQL backend in Golang, you are surely familiar with gqlgen
. This library offers a schema-first generator for GraphQL with a strongly typed workflow that integrates directly with Go idioms. This article will dissect the standard file structure of a gqlgen project, the inner workings of each component, along with examples and a simple request simulation. Let’s start by building a solid foundation in gqlgen.
Why gqlgen?
Gqlgen simplifies the process of building a GraphQL API in Golang with a schema-first workflow. You design the schema first, then gqlgen automatically generates the code stubs, so you can focus on the business logic.
The Schema-First Workflow in Brief
- Create a GraphQL schema (
*.graphql). - Run
go run github.com/99designs/gqlgen generate. - Implement the resolvers stubbed out by gqlgen.
- Run the server and start developing a robust GraphQL API.
The gqlgen File Structure
Let’s take a look at a typical gqlgen project structure, then break down the purpose of each piece.
1.
2├── go.mod
3├── go.sum
4├── gqlgen.yml
5├── graph/
6│ ├── generated/
7│ │ ├── generated.go
8│ │ └── models_gen.go
9│ ├── model/
10│ │ └── models.go
11│ ├── resolver.go
12│ ├── schema.graphqls
13│ └── schema.resolvers.go
14├── server.go
15└── README.mdExplanation of the Structure
| File/Folder | Description |
|---|---|
go.mod, go.sum | Go module dependency management. |
gqlgen.yml | The configuration file for gqlgen. |
graph/generated/ | Code generated from the GraphQL schema (do not edit manually!). |
graph/model/ | Go models that map to the GraphQL schema. |
graph/resolver.go | The main struct that holds all the resolvers. |
graph/schema.graphqls | The GraphQL schema definition (Query, Mutation, Object type, etc.). |
graph/schema.resolvers.go | The implementation of the GraphQL resolver functions (can be split across several files). |
server.go | Initializes and runs the server (usually with http.ServeMux or Gin, etc.). |
README.md | The project description document. |
The gqlgen Workflow Diagram
To understand how the files integrate and how a GraphQL request flows, let’s visualize it with the following mermaid diagram:
flowchart TD
A[Client] -->|Query/Mutation| B[HTTP Endpoint /graphql]
B --> C[Server.go]
C --> D[gqlgen Handler]
D --> E[Generated Code (graph/generated)]
E --> F[Resolver.go]
F --> G[Business Logic in schema.resolvers.go / model/]
G --> H[Data Source (DB/API)]
H --> G
G --> F
F --> E
E --> D
D --> C
C --> B
B --> A
Description:
The client sends a Query/Mutation request to the /graphql endpoint, which is handled by the server and forwarded to the gqlgen handler. This handler leverages the generated code to validate the request and map it to the implemented resolvers. The resolver executes the business logic and fetches or modifies data from the data source before returning the response to the client.
1. Schema Definition: schema.graphqls
This file is the core foundation. Every GraphQL entrypoint, type, input, and even directives are written here.
Example:
1# graph/schema.graphqls
2
3type Query {
4 users: [User!]!
5 user(id: ID!): User
6}
7
8type Mutation {
9 createUser(input: NewUser!): User!
10}
11
12type User {
13 id: ID!
14 name: String!
15 email: String!
16}
17
18input NewUser {
19 name: String!
20 email: String!
21}2. Go Models: model/models.go
Optional – you can tailor your own Go models so they integrate with idiomatic Go code. If no model is provided, gqlgen will generate one automatically in generated/models_gen.go.
1// graph/model/models.go
2package model
3
4type User struct {
5 ID string `json:"id"`
6 Name string `json:"name"`
7 Email string `json:"email"`
8}3. gqlgen Configuration: gqlgen.yml
This file specifies the schema reference, the output location for the generated code, model mapping, and more.
Example:
1schema:
2 - graph/schema.graphqls
3
4exec:
5 filename: graph/generated/generated.go
6 package: generated
7
8model:
9 filename: graph/model/models.go
10 package: model
11
12resolver:
13 layout: follow-schema
14 dir: graph
15 package: graph
16
17models:
18 User:
19 model: graph/model.User
20 NewUser:
21 model: graph/model.NewUser4. Generated Code: graph/generated/
Do not modify the contents of this folder. All files here are regenerated every time you run gqlgen generate.
This is where the binding between GraphQL operations and Go resolvers happens, along with compile-time type safety.
5. Resolvers: graph/resolver.go & schema.resolvers.go
resolver.go contains the main Resolver struct — this is usually where dependencies are injected (DB access, cache, and so on).
1type Resolver struct{
2 users []*model.User
3}Meanwhile, schema.resolvers.go holds the implementation of each resolver (both generated and manual).
A Simple Resolver Example:
1func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
2 return r.users, nil
3}
4
5func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {
6 user := &model.User{
7 ID: fmt.Sprintf("%d", len(r.users)+1),
8 Name: input.Name,
9 Email: input.Email,
10 }
11 r.users = append(r.users, user)
12 return user, nil
13}6. Server Entrypoint: server.go
Initializes and runs the GraphQL HTTP handler.
Example:
1func main() {
2 srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{users: []*model.User{}}}))
3 http.Handle("/graphql", srv)
4 log.Fatal(http.ListenAndServe(":8080", nil))
5}Request-Response Simulation
For example, let’s create a new user in the GraphQL playground/batch.
Request:
1mutation {
2 createUser(input: {name: "Alice", email: "alice@example.com"}) {
3 id
4 name
5 email
6 }
7}1{
2 "data": {
3 "createUser": {
4 "id": "1",
5 "name": "Alice",
6 "email": "alice@example.com"
7 }
8 }
9}Practical Tips
- Never edit files in the
/generatedfolder; always modify code in/modeland the resolvers. - Run
go run github.com/99designs/gqlgen generateevery time you change the schema. - Split resolvers across multiple files as the number of queries/mutations grows.
- Take advantage of dependency injection on the Resolver struct to keep your logic from getting tangled.
- Use a tool like GraphQL Playground for testing.
Summary
gqlgen designs a project structure that is highly idiomatic to Go and neatly separated:
- Schema -> The central API contract that is generated directly into Go code stubs.
- Models -> Can be customized.
- Resolvers -> Where the core logic lives.
- Generated Code -> The “bridge” between GraphQL and Go, which you never need to touch.
This way of clustering the gqlgen file structure helps keep your project scalable and maintainable. You still get the assurance of type safety and the ease of developing a GraphQL API in the Go ecosystem.
Happy exploring with gqlgen—with such a cohesive file architecture, building a Go GraphQL backend has never been this enjoyable and this safe! 🚀