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
gqlgenlibrary
//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"
}

