Go API with echo, gorm, and GraphQL
A simple tutorial of building a Go API with echo, gorm, and 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
:
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:
Create a database
Next, create a database called golang-with-echo-gorm-graphql-example_db
:
CREATE DATABASE `golang-with-echo-gorm-graphql-example_db`;
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:
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:
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:
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: