A Comprehensive Tutorial on Building a GraphQL API in Go using gqlgen and Gorm
Tags: go, tutorial, programming, webdev
Published: 2022-08-15 18:07:00

### Introduction

In today's tutorial we are going to create a graphql api using the gqlgen framework that has a schema first approach, is type safe and still uses codegen to create data types and in our resolvers.

What I find most amazing about gqlgen is that the codegen is configurable, has very complete documentation, is easy to learn and is quite complete in terms of features.

### Prerequisites

Before going further, you are expected to have basic knowledge of these technologies:

- Go
- GraphQL
- ORM's

## Getting Started

Our first step will be to create our project folder:

```sh
mkdir haumea
cd haumea
go mod init haumea
```

Next, let's initialize our project using `gqlgen`:

```sh
go run github.com/99designs/gqlgen init
```

Now we can make some changes regarding the structure of our project. Inside the `graph/` folder we will delete the following folders and files:

- `model/` - this folder held the project's models/data types
- `resolver.go` and `schema.resolvers.go` - these files are related to the implementation of schema resolvers
- `schema.graphqls` - api/project graphql schema

After removing the files and folders we can now edit the `gqlgen.yml` file to add our configuration, which would be the following:

```yml
# @/gqlgen.yml
schema:
  - graph/typeDefs/*.gql

exec:
  filename: graph/generated/generated.go
  package: generated

model:
  filename: graph/customTypes/types_gen.go
  package: customTypes

resolver:
  layout: follow-schema
  dir: graph/resolvers
  package: graph
  filename_template: "{name}.resolvers.go"

autobind:

models:
  ID:
    model:
      - github.com/99designs/gqlgen/graphql.Int
      - github.com/99designs/gqlgen/graphql.ID
      - github.com/99designs/gqlgen/graphql.Int64
      - github.com/99designs/gqlgen/graphql.Int32
  Int:
    model:
      - github.com/99designs/gqlgen/graphql.Int
      - github.com/99designs/gqlgen/graphql.Int64
      - github.com/99designs/gqlgen/graphql.Int32
```

In the above configuration, we made the following changes:

- all our schema files will be inside the `typeDefs` folder and with the `.gql` extension
- the models/data types will be stored inside the `customTypes/` folder in a file called `types_gen.go`
- our resolvers will be generated inside the `resolvers/` folder
- our identifiers (id) when using the graphql type `ID` will map to the golang data type `Int`

With this in mind we can now create the schema for today's project, first let's create the `typeDefs/` folder and create the `todo.gql` file inside it:

```graphql
# @/graph/typeDefs/todo.gql
type Todo {
  id: ID!
  text: String!
  done: Boolean!
}

input TodoInput {
  id: ID!
  text: String!
  done: Boolean!
}

type Mutation {
  createTodo(text: String!): Todo!
  updateTodo(input: TodoInput!): Todo!
  deleteTodo(todoId: ID!): Todo!
}

type Query {
  getTodos: [Todo!]!
  getTodo(todoId: ID!): Todo!
}
```

With the schema created, the next step will be to generate the data types and resolvers for queries and mutations:

```sh
go run github.com/99designs/gqlgen generate
```

Now that we have all this done, we can now move on to the next step which will be to establish the connection to a database:

```sh
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
```

Then we can create a folder called `common/` with the database connection configuration in the `db.go` file:

```go
// @/graph/common/db.go
package common

import (
	"haumea/graph/customTypes"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

func InitDb() (*gorm.DB, error) {
	var err error
	db, err := gorm.Open(sqlite.Open("dev.db"), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		return nil, err
	}
	db.AutoMigrate(&customTypes.Todo{})
	return db, nil
}
```

With the database connection configured we can now create the graphql api context, still inside the `common/` folder we will create the `context.go` file (in this article we will just pass the connection of our database, but you can extend context easily):

```go
// @/graph/common/context.go
package common

import (
	"context"
	"net/http"

	"gorm.io/gorm"
)

type CustomContext struct {
	Database *gorm.DB
}

var customContextKey string = "CUSTOM_CONTEXT"

func CreateContext(args *CustomContext, next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		customContext := &CustomContext{
			Database: args.Database,
		}
		requestWithCtx := r.WithContext(context.WithValue(r.Context(), customContextKey, customContext))
		next.ServeHTTP(w, requestWithCtx)
	})
}

func GetContext(ctx context.Context) *CustomContext {
	customContext, ok := ctx.Value(customContextKey).(*CustomContext)
	if !ok {
		return nil
	}
	return customContext
}
```

Now we need to go to the api's entry file to start connecting to our database and pass the database in the context of our api. This way:

```go
// @/server.go
package main

import (
	"haumea/common"
	"haumea/graph/generated"
	resolvers "haumea/graph/resolvers"
	"log"
	"net/http"
	"os"

	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/99designs/gqlgen/graphql/playground"
)

const defaultPort = "4000"

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = defaultPort
	}

	db, err := common.InitDb()
	if err != nil {
		log.Fatal(err)
	}

	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &resolvers.Resolver{}}))

	customCtx := &common.CustomContext{
		Database: db,
	}

	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
	http.Handle("/query", common.CreateContext(customCtx, srv))

	log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}
```

Now that we have the server configured, the database setup done and with the connection created, we just need to work on the resolvers. 

In each of the resolvers, if we want to access the context we have created, we use the `GetContext()` function to which we will get the instance of our database to perform the CRUD.

```go
// @/graph/resolvers/todo.resolvers.go
package graph

import (
	"context"
	"haumea/common"
	"haumea/graph/customTypes"
	"haumea/graph/generated"
)

func (r *mutationResolver) CreateTodo(ctx context.Context, text string) (*customTypes.Todo, error) {
	context := common.GetContext(ctx)
	todo := &customTypes.Todo{
		Text: text,
		Done: false,
	}
	err := context.Database.Create(&todo).Error
	if err != nil {
		return nil, err
	}
	return todo, nil
}

func (r *mutationResolver) UpdateTodo(ctx context.Context, input customTypes.TodoInput) (*customTypes.Todo, error) {
	context := common.GetContext(ctx)
	todo := &customTypes.Todo{
		ID:   input.ID,
		Text: input.Text,
		Done: input.Done,
	}
	err := context.Database.Save(&todo).Error
	if err != nil {
		return nil, err
	}
	return todo, nil
}

func (r *mutationResolver) DeleteTodo(ctx context.Context, todoID int) (*customTypes.Todo, error) {
	context := common.GetContext(ctx)
	var todo *customTypes.Todo
	err := context.Database.Where("id = ?", todoID).Delete(&todo).Error
	if err != nil {
		return nil, err
	}
	return todo, nil
}

func (r *queryResolver) GetTodos(ctx context.Context) ([]*customTypes.Todo, error) {
	context := common.GetContext(ctx)
	var todos []*customTypes.Todo
	err := context.Database.Find(&todos).Error
	if err != nil {
		return nil, err
	}
	return todos, nil
}

func (r *queryResolver) GetTodo(ctx context.Context, todoID int) (*customTypes.Todo, error) {
	context := common.GetContext(ctx)
	var todo *customTypes.Todo
	err := context.Database.Where("id = ?", todoID).Find(&todo).Error
	if err != nil {
		return nil, err
	}
	return todo, nil
}

func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }

func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
```

To run the server, use the following command:

```sh
go run server.go
```

And to test each of the queries and mutations of this project, just open a new tab in the browser and visit the `http://localhost:4000/`.

## Conclusion

As usual, I hope you enjoyed the article and that it helped you with an existing project or simply wanted to try it out.

If you found a mistake in the article, please let me know in the comments so I can correct it. Before finishing, if you want to access the source code of this article, I leave [here](https://github.com/FranciscoMendes10866/gqlgen-gorm) the link to the github repository.