Go API with echo, gorm, and GraphQL

A simple tutorial of building a Go API with echo, gorm, and GraphQL.

Manato Kuroda
7 min readNov 1, 2019
Go with GraphQL

GraphQL has been around these days and is usually described as a new type in the field of API technologies. Even though you’ve been constructing and using RESTful API’s for quite some time, you can find it simple and useful.

In this article, we’re going to highlight how to set up GraphQL API in Go with some dependencies.

Example Repo

Here is a link to the codebase in full for reference:

Dependencies

Some dependencies are required to grab in this post so install them:

go get github.com/oxequa/realize
go get github.com/labstack/echo
go get github.com/jinzhu/gorm
go get github.com/jinzhu/gorm/dialects/mysql
go get bitbucket.org/liamstask/goose/cmd/goose
go get github.com/graphql-go/graphql
go get github.com/graphql-go/handler

Setup MySQL with Docker

Getting started with it quickly, we use Docker and setup mysql 8.0.

Add a docker-compose.yml in your root project:

version: '3'
services:
mysql:
image: mysql:8.0
volumes:
- mysqldata:/var/lib/mysql
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_ci --default-authentication-plugin=mysql_native_password
environment:
MYSQL_USER: root
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
volumes:
mysqldata:
driver: local

And run it:

docker-compose up

Set up an echo server

Let’s set up an echo server with a very simple API endpoint.

Create main.go :

package main

import (
"golang-with-echo-gorm-graphql-example/handler"
"log"

"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)

func main() {
e := echo.New()

e.Use(middleware.Logger())
e.Use(middleware.Recover())

e.GET("/", handler.Welcome())

if err := e.Start(":3000"); err != nil {
log.Fatalln(err)
}

}

And add handler/handler.go :

package handler

import (
"net/http"

"github.com/labstack/echo"
)

func Welcome() echo.HandlerFunc {
return func(c echo.Context) error {
return c.String(http.StatusOK, "Welcome!")
}
}

Run the code and you’ll see a welcome message at http://localhost:3000 :

Welcome message

Set up realize

For fast development, we can use realize for hot reloading.

In your root project, run it:

realize start --server

And you’ll see a .realize.yaml at the root project. To work with an echo server, edit it:

settings:
legacy:
force: false
interval: 0s
schema:
- name: golang-with-echo-gorm-graphql-example
path: .
commands:
run:
status: true

watcher:
extensions:
- go
paths:
- /
ignore:
paths:
- .git
- .realize
- vendor

And restart it:

realize start --server

You’ll get like this:

Running..
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v3.3.10-dev
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on [::]:3000

This will watch any changes within the project and it will automatically restart the server if it detects one.

They also provide a console server at http://localhost:5201 to see what your server is going on:

realize server

Create a database

Next, create a database called golang-with-echo-gorm-graphql-example_db :

CREATE DATABASE `golang-with-echo-gorm-graphql-example_db`;
Database

Create a User table

I like using a database migration tool when I change anything on the database, as Active Record does in Rails. goose is one of the migration tools in Go and provides the ability to manage database schema by SQL or even Go functions.

To set it up, let’s create db/dbconfig.yml :

development:
driver: mysql
open: root:root@tcp(127.0.0.1:3306)/golang-with-echo-gorm-graphql-example_db

To make sure of correctly connecting to:

goose status

And you’ll see it:

$ goose statusgoose: status for environment 'development'
Applied At Migration
=======================================

Next, create a User table through the command:

goose create CreateUsers sql

And you’ll see the migration file generated in db/migrations :

-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied


-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back

You can add a SQL to:

-- +goose Up
-- +goose StatementBegin
CREATE TABLE users (
id INT NOT NULL AUTO_INCREMENT,
name varchar(255) DEFAULT NULL,
age varchar(255) DEFAULT NULL,
created_at datetime DEFAULT NULL,
updated_at datetime DEFAULT NULL,
deleted_at timestamp NULL DEFAULT NULL,
INDEX user_id (id),
PRIMARY KEY(id)
) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4;

-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
DROP TABLE users;
-- +goose StatementEnd

And execute the SQL:

goose up

And a users table is created in the database:

User table

Add some users to users table:

INSERT INTO `users` (`id`, `name`, `age`, `created_at`, `updated_at`, `deleted_at`) VALUES (1, 'name1', '20', '2019-01-01 00:00:00', '2019-01-01 00:00:00', NULL);
INSERT INTO `users` (`id`, `name`, `age`, `created_at`, `updated_at`, `deleted_at`) VALUES (2, 'name2', '30', '2019-01-01 00:00:00', '2019-01-01 00:00:00', NULL);
INSERT INTO `users` (`id`, `name`, `age`, `created_at`, `updated_at`, `deleted_at`) VALUES (3, 'name3', '40', '2019-01-01 00:00:00', '2019-01-01 00:00:00', NULL);

Connect to Mysql

We mostly are done with setting up the server and the database. Next, we’ll connect to MySQL through gorm module.

Let’s create a database/db.go to connect Mysql:

package datastore

import (
"github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)

func NewDB() (*gorm.DB, error) {
DBMS := "mysql"
mySqlConfig := &mysql.Config{
User: "root",
Passwd: "root",
Net: "tcp",
Addr: "127.0.0.1:3306",
DBName: "golang-with-echo-gorm-graphql-example_db",
AllowNativePasswords: true,
Params: map[string]string{
"parseTime": "true",
},
}

return gorm.Open(DBMS, mySqlConfig.FormatDSN())
}

Generate a db connection in main.go :

package main

import (
"golang-with-echo-gorm-graphql-example/datastore"
"golang-with-echo-gorm-graphql-example/handler"
"log"

"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)

func main() {
db, err := datastore.NewDB()
logFatal(err)

db.LogMode(true)
defer db.Close()


e := echo.New()

e.Use(middleware.Logger())
e.Use(middleware.Recover())

e.GET("/", handler.Welcome())

err = e.Start(":3000")
logFatal(err)
}

func logFatal(err error) {
if err != nil {
log.Fatalln(err)
}
}

Create a user model in domain/model/user.go :

package model

import "time"

type User struct {
ID uint `gorm:"primary_key" json:"id"`
Name string `json:"name"`
Age string `json:"age"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt time.Time `json:"deleted_at"`
}

func (User) TableName() string { return "users" }

Add GetUsers in handler/handler.go :

func GetUsers(db *gorm.DB) echo.HandlerFunc {
return func(c echo.Context) error {
var u []*model.User

if err := db.Find(&u).Error; err != nil {
// error handling here
return err
}

return c.JSON(http.StatusOK, u)
}
}

And add users endpoint to routers in main.go :

e.GET("/", handler.Welcome())
e.GET("/users", handler.GetUsers(db))

Access http://localhost:3000/users and you’ll see some users result:

Users result

Set up GraphQL

Next, we’re going to transport this users endpoint to GraphQL using graphql-go and graphql-go/handler.

Let’s create graphql/handler.go :

package graphql

import (
"github.com/graphql-go/graphql"
"github.com/graphql-go/handler"
"github.com/jinzhu/gorm"
)

func NewHandler(db *gorm.DB) (*handler.Handler, error) {
schema, err := graphql.NewSchema(
graphql.SchemaConfig{
Query: newQuery(db),
},
)
if err != nil {
return nil, err
}

return handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
GraphiQL: true,
}), nil
}

And create graphql/query.go :

package graphql

import (
"golang-with-echo-gorm-graphql-example/graphql/field"

"github.com/graphql-go/graphql"
"github.com/jinzhu/gorm"
)

func newQuery(db *gorm.DB) *graphql.Object {
return graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"Users": field.NewUsers(db),
},
})
}

Next, create users fields, graphql/field/users.go :

package field

import (
"golang-with-echo-gorm-graphql-example/domain/model"

"github.com/graphql-go/graphql"
"github.com/jinzhu/gorm"
)

var user = graphql.NewObject(
graphql.ObjectConfig{
Name: "User",
Fields: graphql.Fields{
"id": &graphql.Field{Type: graphql.ID},
"name": &graphql.Field{Type: graphql.String},
"age": &graphql.Field{Type: graphql.String},
"createdAt": &graphql.Field{Type: graphql.String},
"updatedAt": &graphql.Field{Type: graphql.String},
"deletedAt": &graphql.Field{Type: graphql.String},
},
Description: "Users data",
},
)

func NewUsers(db *gorm.DB) *graphql.Field {
return &graphql.Field{
Type: graphql.NewList(user),
Resolve: func(p graphql.ResolveParams) (i interface{}, e error) {
var u []*model.User
if err := db.Find(&u).Error; err != nil {
// do something
}

return u, nil
},
Description: "user",
}
}

And replace the users endpoint with graphql in main.go :

package main

import (
"golang-with-echo-gorm-graphql-example/datastore"
"golang-with-echo-gorm-graphql-example/graphql"
"golang-with-echo-gorm-graphql-example/handler"
"log"

"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)

func main() {
db, err := datastore.NewDB()
logFatal(err)

db.LogMode(true)
defer db.Close()

e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.GET("/", handler.Welcome())

// graphql endpoint here
h, err := graphql.NewHandler(db)
logFatal(err)
e.POST("/graphql", echo.WrapHandler(h))


err = e.Start(":3000")
logFatal(err)
}

func logFatal(err error) {
if err != nil {
log.Fatalln(err)
}
}

To work with echo handler, wrap graphql hander with echo.WrapHandler .

It should get the users at http://localhost:3000/query through POST. You’ll see at GraphiQL:

GraphiQL

Conclusion

That’s it. We got very simple users endpoint and learned how to resolve it through echo and GraphQL. Hopefully, this will be useful to help you get off the ground with GraphQL API in Go.

You can view the final working example at:

--

--