DEV Community

Cover image for What I've learned this week?
Bervianto Leo Pratama
Bervianto Leo Pratama

Posted on

What I've learned this week?

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. I use this to help me to implement the GraphQL.
  2. echo. I use this library to help me implement the routing and HTTP handler.
  3. gorm. 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 logo bervProject / go-microservice-boilerplate

Go Microservice Boilerplate

go-microservice-boilerplate

codecov

Go Microservice Boilerplate

Environment

DB_CONNECTION_STRING=
PORT=
Enter fullscreen mode Exit fullscreen mode

Build

go build
Enter fullscreen mode Exit fullscreen mode

Run Locally

go run server.go
Enter fullscreen mode Exit fullscreen mode

Test

go test ./... -cover
Enter fullscreen mode Exit fullscreen mode

LICENSE

MIT




Here is the code:

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"))
}
Enter fullscreen mode Exit fullscreen mode

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.
// 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"))
}
Enter fullscreen mode Exit fullscreen mode
  • Initiate Database and ORM
// 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
}
Enter fullscreen mode Exit fullscreen mode
  • Initiate GraphQL Schema, Queries, and Mutations
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
}
Enter fullscreen mode Exit fullscreen mode

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

Other Related Articles

You might want to see these articles too.

Top comments (0)