Table of Contents
The goal: setting up GraphQL server using Gqlgen library.
We are going to set up GraphQL server with Users, and create queries to retrieve the users by id or user name.
Schema-first approach – means that instead of using library apis (code-first approach) we are going to write our schema manually using the GraphQL schema definition language.
Setting up the project
- Create project directory
mkdir gqlgen-tutorial
- Navigate to the folder
cd gqlgen-tutorial
- Initialize go project.
go mod init gqlgen-tutorial
- Create tools.go and add
gqlgen
library
//go:build tools // +build tools package tools import _ "github.com/99designs/gqlgen"
- Add the library
go mod tidy
Initializing Gqlgen with boilerplate schema and resolvers
gqlgen has handy command to Initialize gqlgen config and generate the models.
go run github.com/99designs/gqlgen init
This will create server.go
the server starting point and ./graph
directory with a couple of files including schema.graphqls
and if you open it you will see that it comes with pre-defined example schema which we are going to remove later and start from scratch.
graph/model/model_gen.go – is auto generated file containing structure of defined by the schema file graph/schema.graphqls
graph/generated.go – this is a file with generated code that injects context and middleware for each query and mutation.
We should not modify these files since they will be modified by gqlgen
as we update the schema. Instead, we should edit these files:
graph/schema.graphqls – GraphQL schema file where types, queries, and mutations are defined.
graph/resolver.go – resolver functions for queries and mutations defined in schema.graphqls
At this point we could start the server and see the playground and the schema.
go run ./server.go
Sometimes you might see an error and in this case just run go mod tidy
again.
Defining queries
First let’s remove boilerplate schema from graph/schema.graphqls
and add our new schema
graph/schema.graphqls
# GraphQL schema example # # https://gqlgen.com/getting-started/ type User { id: ID! name: String! userType: String! } type Query { getUser(id:ID!): User } input NewUser { userId: String! userName: String! userType: String! } type Mutation { createUser(input: NewUser!): User! }
Generate code and running the API
We will now generate code, which will update the following files using the information we provided in the schema file:
- schema.resolvers.go
- model/models_gen.go
- generated/generated.go
Delete the example code in schema.resolvers.go
and then run the following command:
go run github.com/99designs/gqlgen generate
If we run the server we will run into an error because we didn’t define any resolver yet.
Defining the backend to fetch and store values
In resolver.go:
– import qlgen-tutorial/graph/model
. line: 3
– declare a Hash Map that we will use to store users. Line 10
package graph import "gqlgen-tutorial/graph/model" // This file will not be regenerated automatically. // // It serves as dependency injection for your app, add any dependencies you require here. type Resolver struct{ UsersStore map[string]model.User }
We defined UserStore
of type map which essentially is a hash-map with keys of type string and values of type model.User.
In schema.resolvers.go
, we are going to modify the boilerplate methods: CreateUser
and GetUser
package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. // Code generated by github.com/99designs/gqlgen version v0.17.24 import ( "context" "fmt" "gqlgen-tutorial/graph/model" ) // CreateUser is the resolver for the createUser field. func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) { // create new user to be added in r.Resolver.UsersStore and returned var user model.User if len(r.Resolver.UsersStore) == 0 { // create new UserStore hash map if it does not exist r.Resolver.UsersStore = make(map[string]model.User) } // set up new user attributes user.ID = input.UserID user.Name = input.UserName user.UserType = input.UserType // adds newly created user into the resolver's UserStore r.Resolver.UsersStore[input.UserID] = user return &user, nil } // GetUser is the resolver for the getUser field. func (r *queryResolver) GetUser(ctx context.Context, id string) (*model.User, error) { fmt.Println(r.Resolver.UsersStore) // retrieve user from the store user, isOk := r.Resolver.UsersStore[id] if !isOk { return nil, fmt.Errorf("not found") } return &user, nil } // Mutation returns MutationResolver implementation. func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} } // Query returns QueryResolver implementation. func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } type mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver }
GetUser
is pretty straight forward: it gets user id and returns the user from the hash map store.
CreateUser
first checks if UserStore is initialized. Line 19. If CreateUser length is 0 it initializes the hash-map. Line 21.
Then it gets the values for username, id and userType from the input
parameter and sets up the new user, stores it into the UserStore
, line 29, and returns it.
Testing
Creating user
Mutation:
mutation createUserMutation($input: NewUser!) { createUser(input: $input) { name id } }
Variables:
{ "input": { "userId": "1", "userName": "John", "userType": "admin", "userGender": "male" } }
Query for user with id
Query:
query getUserQuery($nid: ID!) { getUser(id:$nid) { id name userType userGender } }
Variables:
{ "nid": "1" }