13 Building Your First Resolver in graphql-go
13 Building Your First Resolver in graphql-go: A Complete Guide for Beginners
If you want to build a modern API that’s flexible and efficient, GraphQL has become almost mandatory in today’s developer toolset. Unlike REST, GraphQL lets the client decide exactly which data it wants to fetch in a single request. One of the most popular libraries in the Go ecosystem is graphql-go
, which offers a straightforward way to build a GraphQL API in Go.
In this article, we’ll walk through the process of building your first resolver in graphql-go step by step. We’ll explain the concept, the basic implementation, how to add mock data, and break down the code structure and execution flow, complete with code examples, simulations, a flow diagram, and a table of query results.
Let’s get started!
What Is a Resolver in GraphQL?
In GraphQL, a resolver is a function responsible for processing and returning data for each field in the schema. Every field on Query, Mutation, or any other type is executed using one of these resolver functions.
A Simple Analogy
If the schema is the “structure of the question,” then the resolver is the “way you answer” that question against a database, another API, or in-memory data.
Table: GraphQL Schema vs. Resolver
| Component | Role | Example |
|---|---|---|
| Schema | Defines the question | type Query { user(id:ID):User } |
| Resolver | Answers the question | func (r *Resolver) User(…) … |
Step 1: Set Up the Project
First, initialize a new Go project.
1go mod init github.com/username/graphql-go-demo
2go get github.com/graph-gophers/graphql-go
3go get github.com/graph-gophers/graphql-go/relayStep 2: Define the GraphQL Schema
Let’s create a schema.graphql file:
1type Query {
2 hello: String!
3 user(id: ID!): User
4}
5
6type User {
7 id: ID!
8 name: String!
9 email: String!
10}The schema above has:
- A
helloquery that returns a string - A
userquery that accepts anidargument and returns a User with the fieldsid,name, andemail.
Step 3: Mock Data Mode
Create some dummy (mock) data in a model.go file:
1package main
2
3type User struct {
4 ID string
5 Name string
6 Email string
7}
8
9// Simulated dummy database
10var users = []*User{
11 {ID: "1", Name: "Alice", Email: "alice@example.com"},
12 {ID: "2", Name: "Bob", Email: "bob@example.com"},
13 {ID: "3", Name: "Charlie", Email: "charlie@example.com"},
14}Step 4: Building the Resolver
Now for the interesting part: we’ll build a resolver that maps the GraphQL schema above to our Go data.
Create a resolver.go file:
1package main
2
3import (
4 "context"
5)
6
7type Resolver struct{}
8
9func (r *Resolver) Hello(ctx context.Context) (string, error) {
10 return "Halo dari resolver pertama!", nil
11}
12
13func (r *Resolver) User(ctx context.Context, args struct{ ID string }) (*userResolver, error) {
14 for _, u := range users {
15 if u.ID == args.ID {
16 return &userResolver{u}, nil
17 }
18 }
19 return nil, nil // or you could use a custom error
20}
21
22type userResolver struct {
23 u *User
24}
25
26func (ur *userResolver) ID() string { return ur.u.ID }
27func (ur *userResolver) Name() string { return ur.u.Name }
28func (ur *userResolver) Email() string { return ur.u.Email }Notes:
- Every method (for example
Hello,User) MUST have a capitalized (exported) name, and its arguments must follow the order ofcontext.Contextfirst, then the schema arguments (if any). - For the
Usertype, we create our own resolver (userResolver) because graphql-go requires a dedicated resolver struct for each type that exposes fields.
Step 5: Set Up the HTTP Handler
In main.go:
1package main
2
3import (
4 "io/ioutil"
5 "log"
6 "net/http"
7
8 "github.com/graph-gophers/graphql-go"
9 "github.com/graph-gophers/graphql-go/relay"
10)
11
12func main() {
13 schemaData, err := ioutil.ReadFile("schema.graphql")
14 if err != nil {
15 log.Fatalf("failed to read schema: %v", err)
16 }
17
18 schema := graphql.MustParseSchema(string(schemaData), &Resolver{})
19
20 http.Handle("/graphql", &relay.Handler{Schema: schema})
21
22 log.Println("GraphQL server listening at http://localhost:8080/graphql")
23 log.Fatal(http.ListenAndServe(":8080", nil))
24}Step 6: Your First Query to the Resolver
Run the server:
1go run .Try using a tool like GraphiQL
, Insomnia, or curl:
hello query:
1query {
2 hello
3}Response:
1{
2 "data": {
3 "hello": "Halo dari resolver pertama!"
4 }
5}user query:
1query {
2 user(id: "2") {
3 id
4 name
5 email
6 }
7}Response:
1{
2 "data": {
3 "user": {
4 "id": "2",
5 "name": "Bob",
6 "email": "bob@example.com"
7 }
8 }
9}Resolver Execution Flow
Let’s visualize the flow to make it easier to understand:
flowchart TD
Client -->|Query| GraphQLServer
GraphQLServer -->|Parse Schema| ResolverFunctions
ResolverFunctions -->|Ambil Data| DatabaseSimulasi
ResolverFunctions -->|Return| GraphQLServer
GraphQLServer -->|Response JSON| Client
Explanation:
- The client sends a query to the GraphQL endpoint.
- The server parses it and finds the resolver matching the field.
- The resolver runs its logic, pulling data from the (simulated) database.
- The data is returned in JSON format according to the client’s request.
Table: Simulated User Queries
| Query | Argument | Result |
|---|---|---|
hello | - | “Halo dari resolver pertama!” |
user(id: "1") | id = “1” | {“id”:“1”, “name”:“Alice”, …} |
user(id: "3") | id = “3” | {“id”:“3”, “name”:“Charlie”, …} |
user(id: "999") | id = “999” | null |
graphql-go Resolver Best Practices
- Separate the schema, resolver, and model. This keeps the code clean and easy to maintain.
- Use context to propagate auth/logging data, for example injecting the user ID or trace ID from the headers into the resolver.
- Test your resolvers with unit tests using mock data to make sure the output matches expectations.
- Data mutations (such as create/update/delete) should use a Mutation; the resolver pattern is similar to a Query.
- Error handling: return descriptive errors, for example when a user is not found, and surface them to the client through the
errorsfield in the payload.
Conclusion
Building a resolver in graphql-go is very straightforward and follows idiomatic Go principles. Once you understand resolvers, you can easily control how data is processed, fetched, and returned according to the client’s request. This resolver model is also very easy to extend into something more complex: for example, fetching data asynchronously, aggregating data across services, or validating user access.
With this article, you’ve learned how to:
- Write a GraphQL schema and map it to Go resolvers
- Create mock data and run your first query against a GraphQL endpoint
- Understand the resolver execution flow visually
- Apply basic best practices when developing a GraphQL API
Have fun building your first resolver in graphql-go! If you have any questions or suggestions for follow-up topics, don’t hesitate to ask in the comments. 🚀
References:
Happy coding! 👨💻👩💻