What I've learned this week?
Tags: programming, todayilearned, go, graphql
Published: 2022-03-31 16:36:37

I wonder how to implement GraphQL in Golang. I tried to search and found some libraries that would help me. Here is the list:

1. [graphql-go](https://github.com/graphql-go/graphql). I use this to help me to implement the GraphQL.
2. [echo](https://echo.labstack.com/). I use this library to help me implement the routing and HTTP handler.
3. [gorm](https://gorm.io/index.html). I use this library to help me implement the database side. This library is an ORM.

## Code ???

I'll recommend you to visit my Github repository.

{% github bervProject/go-microservice-boilerplate %}

Here is the code:

```go
package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/graphql-go/graphql"
	"github.com/labstack/echo/v4"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

type PostData struct {
	Query     string                 `json:"query"`
	Operation string                 `json:"operation"`
	Variables map[string]interface{} `json:"variables"`
}

type User struct {
	ID           uint
	Name         string
	Email        *string
	Age          uint8
	Birthday     *time.Time
	MemberNumber sql.NullString
	ActivatedAt  sql.NullTime
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

func executeQuery(query string, schema graphql.Schema) *graphql.Result {
	result := graphql.Do(graphql.Params{
		Schema:        schema,
		RequestString: query,
	})
	if len(result.Errors) > 0 {
		fmt.Printf("wrong result, unexpected errors: %v", result.Errors)
	}
	return result
}

func main() {
	dsn := os.Getenv("DB_CONNECTION_STRING")
	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatalf("failed to connect database, error: %v", err)
	}
	db.AutoMigrate(&User{})
	// Schema
	userType := graphql.NewObject(
		graphql.ObjectConfig{
			Name: "User",
			Fields: graphql.Fields{
				"id": &graphql.Field{
					Type: graphql.Int,
				},
				"name": &graphql.Field{
					Type: graphql.String,
				},
			},
		},
	)

	fields := graphql.Fields{
		"hello": &graphql.Field{
			Type: graphql.String,
			Resolve: func(p graphql.ResolveParams) (interface{}, error) {
				return "world", nil
			},
		},
	}
	mutationFields := graphql.Fields{
		"createUser": &graphql.Field{
			Type:        userType,
			Description: "Create New User",
			Args: graphql.FieldConfigArgument{
				"name": &graphql.ArgumentConfig{
					Type: graphql.NewNonNull(graphql.String),
				},
			},
			Resolve: func(params graphql.ResolveParams) (interface{}, error) {
				user := User{
					Name: params.Args["name"].(string),
				}
				result := db.Create(&user)
				if result.Error != nil {
					return nil, result.Error
				}
				return user, nil
			},
		},
	}
	rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
	rootMutation := graphql.ObjectConfig{Name: "RootMutation", Fields: mutationFields}
	schemaConfig := graphql.SchemaConfig{
		Query:    graphql.NewObject(rootQuery),
		Mutation: graphql.NewObject(rootMutation),
	}
	schema, err := graphql.NewSchema(schemaConfig)
	if err != nil {
		log.Fatalf("failed to create new schema, error: %v", err)
	}
	e := echo.New()
	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello, World!")
	})
	e.POST("/graphql", func(c echo.Context) error {
		data := new(PostData)
		if err := c.Bind(data); err != nil {
			return err
		}
		result := graphql.Do(graphql.Params{
			Schema:         schema,
			RequestString:  data.Query,
			VariableValues: data.Variables,
			OperationName:  data.Operation,
		})
		return c.JSON(http.StatusOK, result)
	})
	e.Logger.Fatal(e.Start(":1323"))
}
```

## TLDR

### Important Parts

We will have 3 important parts. Let's go.

* Initiate Echo, define GraphQL Path and common execution to call GraphQL Query or GraphQL Mutation.

```go
// data struct to represent the request of GraphQL.
type PostData struct {
	Query     string                 `json:"query"`
	Operation string                 `json:"operation"`
	Variables map[string]interface{} `json:"variables"`
}

func main() {
  // ... rest of codes

  // initiate echo
  e := echo.New()

  // initiate path and handler
  e.POST("/graphql", func(c echo.Context) error {
        // bind body message of query/mutation to PostData
	data := new(PostData)
	if err := c.Bind(data); err != nil {
		return err
	}
        // call the graphql using the data provided and already binded with PostData
	result := graphql.Do(graphql.Params{
		Schema:         schema,
		RequestString:  data.Query,
		VariableValues: data.Variables,
		OperationName:  data.Operation,
	})
        // return the result as it is and expect will success
        // we will learn in the next post about error handling
	return c.JSON(http.StatusOK, result)
  })
	e.Logger.Fatal(e.Start(":1323"))
}
```

* Initiate Database and ORM

```go
// sample table definition
type User struct {
	ID           uint
	Name         string
	Email        *string
	Age          uint8
	Birthday     *time.Time
	MemberNumber sql.NullString
	ActivatedAt  sql.NullTime
	CreatedAt    time.Time
	UpdatedAt    time.Time
}


func main() {
        // you may change the os.Getenv to string directly for testing in case you don't have the env.
        dsn := os.Getenv("DB_CONNECTION_STRING")
        // initiate the database connection
        // since I use postgres, so the open come from postgres driver, you may use other drivers if you are using another database.
	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatalf("failed to connect database, error: %v", err)
	}
        // we are doing auto migrate
	db.AutoMigrate(&User{})
        // ... rest of codes
}
```

* Initiate GraphQL Schema, Queries, and Mutations

```go
func main() {
       // ... rest of codes
       
      // initiate user object
        userType := graphql.NewObject(
		graphql.ObjectConfig{
			Name: "User",
			Fields: graphql.Fields{
				"id": &graphql.Field{
					Type: graphql.Int,
				},
				"name": &graphql.Field{
					Type: graphql.String,
				},
			},
		},
	)

        // sample to define queries
	fields := graphql.Fields{
		"hello": &graphql.Field{
			Type: graphql.String,
			Resolve: func(p graphql.ResolveParams) (interface{}, error) {
				return "world", nil
			},
		},
	}
        // sample to define mutations
	mutationFields := graphql.Fields{
		"createUser": &graphql.Field{
			Type:        userType,
			Description: "Create New User",
			Args: graphql.FieldConfigArgument{
				"name": &graphql.ArgumentConfig{
					Type: graphql.NewNonNull(graphql.String),
				},
			},
			Resolve: func(params graphql.ResolveParams) (interface{}, error) {
				user := User{
					Name: params.Args["name"].(string),
				}
				result := db.Create(&user)
				if result.Error != nil {
					return nil, result.Error
				}
				return user, nil
			},
		},
	}
        // merge the queries into rootQuery
	rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
        // merge the mutation into rootMutation
	rootMutation := graphql.ObjectConfig{Name: "RootMutation", Fields: mutationFields}
        // merge query & mutation to 1 schema
	schemaConfig := graphql.SchemaConfig{
		Query:    graphql.NewObject(rootQuery),
		Mutation: graphql.NewObject(rootMutation),
	}
        // initiate the schema
	schema, err := graphql.NewSchema(schemaConfig)
	if err != nil {
		log.Fatalf("failed to create new schema, error: %v", err)
	}
       // ... rest of codes
}
```

## Thank you

I just want to share what I've learned this week. I learn a lot and try to understand about go, especially if I want to code with GraphQL. I hope you enjoy my sharing. If you have any questions, let me know. We will learn together. Thank you!

![GIF Happy](https://media.giphy.com/media/cWOyrcPSiCavaEicec/giphy.gif)

## Other Related Articles

You might want to see these articles too.

{% embed https://dev.to/aws-builders/lets-get-started-with-echo-go-aws-sdk-for-go-and-aws-s3-2ac3 %}

{% embed https://dev.to/berviantoleo/deploy-net-6-web-api-docker-image-to-amazon-ecr-using-circleci-e5h %}