Go API with echo, gorm, and GraphQL

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

Go with GraphQL

Example Repo

Dependencies

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

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
docker-compose up

Set up an echo server

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)
}

}
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!")
}
}
Welcome message

Set up realize

realize start --server
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
realize start --server
Running..
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v3.3.10-dev
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on [::]:3000
realize server

Create a database

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

Create a User table

development:
driver: mysql
open: root:root@tcp(127.0.0.1:3306)/golang-with-echo-gorm-graphql-example_db
goose status
$ goose statusgoose: status for environment 'development'
Applied At Migration
=======================================
goose create CreateUsers sql
-- +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
-- +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
goose up
User 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

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())
}
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)
}
}
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" }
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)
}
}
e.GET("/", handler.Welcome())
e.GET("/users", handler.GetUsers(db))
Users result

Set up GraphQL

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
}
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),
},
})
}
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",
}
}
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)
}
}
GraphiQL

Conclusion

Web Developer, TypeScript, React, React Native, Vue.js, Go, Swift, and Ruby on Rails https://manatoworks.me/

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store