HelloKoding

Practical coding guides

Golang REST API Example with Wire, Gin, Gorm and MySQL

This tutorial will walk you through the steps to build a CRUD RESTful APIs example by using Golang, Go Modules, Wire, Gin, Gorm and MySQL

About the tech stack

Prerequisites

  • This example’s tested on Go 1.13 and MySQL 8+

Init project structure and dependencies

  • The project’s packaged in business functionality instead of technicalities
├── product
│   ├── product.go
│   ├── product_api.go
│   ├── product_dto.go
│   ├── product_mapper.go
│   ├── product_repository.go
│   └── product_service.go
├── go.mod
├── go.sum
├── main.go
├── wire.go
└── wire_gen.go
  • The dependencies are defined in the go.mod file at the project root directory

go.mod

module rest-gin-gorm

go 1.13

require (
	github.com/gin-gonic/gin v1.4.0
	github.com/google/wire v0.3.0
	github.com/jinzhu/gorm v1.9.10
)
  • You can find the wire.go and wire_gen.go in the latter part of this tutorial

Create model, repository and service

Create model

product.go

package product

import "github.com/jinzhu/gorm"

type Product struct {
	gorm.Model
	Code string
	Price uint
}

gorm.Model is an embedded model defining common fields including ID, CreatedAt, UpdatedAt and DeletedAt

Create repository

product_repository.go

package product

import (
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

type ProductRepository struct {
	DB *gorm.DB
}

func ProvideProductRepostiory(DB *gorm.DB) ProductRepository {
	return ProductRepository{DB: DB}
}

func (p *ProductRepository) FindAll() []Product {
	var products []Product
	p.DB.Find(&products)

	return products
}

func (p *ProductRepository) FindByID(id uint) Product {
	var product Product
	p.DB.First(&product, id)

	return product
}

func (p *ProductRepository) Save(product Product) Product {
	p.DB.Save(&product)

	return product
}

func (p *ProductRepository) Delete(product Product) {
	p.DB.Delete(&product)
}

Create service

product_service.go

package product

type ProductService struct {
	ProductRepository ProductRepository
}

func ProvideProductService(p ProductRepository) ProductService {
	return ProductService{ProductRepository: p}
}

func (p *ProductService) FindAll() []Product {
	return p.ProductRepository.FindAll()
}

func (p *ProductService) FindByID(id uint) Product {
	return p.ProductRepository.FindByID(id)
}

func (p *ProductService) Save(product Product) Product {
	p.ProductRepository.Save(product)

	return product
}

func (p *ProductService) Delete(product Product) {
	p.ProductRepository.Delete(product)
}

Create DTO, mapper and API

Create DTO

product_dto.go

package product

type ProductDTO struct {
	ID		uint 	`json:"id,string,omitempty"`
	Code	string	`json:"code"`
	Price	uint	`json:"price,string"`
}

Create Mapper

product_mapper.go

package product

func ToProduct(productDTO ProductDTO) Product {
	return Product{Code: productDTO.Code, Price: productDTO.Price}
}

func ToProductDTO(product Product) ProductDTO {
	return ProductDTO{ID: product.ID, Code: product.Code, Price: product.Price}
}

func ToProductDTOs(products []Product) []ProductDTO {
	productdtos := make([]ProductDTO, len(products))

	for i, itm := range products {
		productdtos[i] = ToProductDTO(itm)
	}

	return productdtos
}

Create API

product_api.go

package product

import (
	"strconv"
	"log"
	"net/http"
	"github.com/gin-gonic/gin"
)

type ProductAPI struct {
	ProductService ProductService
}

func ProvideProductAPI(p ProductService) ProductAPI {
	return ProductAPI{ProductService: p}
}

func (p *ProductAPI) FindAll(c *gin.Context) {
	products := p.ProductService.FindAll()

	c.JSON(http.StatusOK, gin.H{"products": ToProductDTOs(products)})
}

func (p *ProductAPI) FindByID(c *gin.Context) {
	id, _ :=  strconv.Atoi(c.Param("id"))
	product := p.ProductService.FindByID(uint(id))
	
	c.JSON(http.StatusOK, gin.H{"product": ToProductDTO(product)})
}

func (p *ProductAPI) Create(c *gin.Context) {
	var productDTO ProductDTO
	err := c.BindJSON(&productDTO)
	if err != nil {
		log.Fatalln(err)
		c.Status(http.StatusBadRequest)
		return
	}

	createdProduct := p.ProductService.Save(ToProduct(productDTO))

	c.JSON(http.StatusOK, gin.H{"product": ToProductDTO(createdProduct)})
}

func (p *ProductAPI) Update(c *gin.Context) {
	var productDTO ProductDTO
	err := c.BindJSON(&productDTO)
	if err != nil {
		log.Fatalln(err)
		c.Status(http.StatusBadRequest)
		return
	}

	id, _ :=  strconv.Atoi(c.Param("id"))
	product := p.ProductService.FindByID(uint(id))
	if product == (Product{}) {
		c.Status(http.StatusBadRequest)
		return
	}

	product.Code = productDTO.Code
	product.Price = productDTO.Price
	p.ProductService.Save(product)

	c.Status(http.StatusOK)
}

func (p *ProductAPI) Delete(c *gin.Context) {
	id, _ :=  strconv.Atoi(c.Param("id"))
	product := p.ProductService.FindByID(uint(id))
	if product == (Product{}) {
		c.Status(http.StatusBadRequest)
		return
	}

	p.ProductService.Delete(product)

	c.Status(http.StatusOK)
}

Wire things together

Create wire.go to inject dependencies

wire.go

package main

import (
	"github.com/jinzhu/gorm"
	"github.com/google/wire"
	"rest-gin-gorm/product"
)

func initProductAPI(db *gorm.DB) product.ProductAPI {
	wire.Build(product.ProvideProductRepostiory, product.ProvideProductService, product.ProvideProductAPI)

	return product.ProductAPI{}
}

In the command line, run $GOPATH/bin/wire at the same location of wire.go to generate the wire_gen.go file

Create main.go to bootstrap the app

main.go

package main

import (
	"os"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"github.com/gin-gonic/gin"

	"rest-gin-gorm/product"
)

func initDB() *gorm.DB{
	db, err := gorm.Open("mysql", os.Getenv("DB_URL"))
	if err != nil {
		panic(err)
	}

	db.AutoMigrate(&product.Product{})

	return db
}

func main() {
	db := initDB()
	defer db.Close()

	productAPI := InitProductAPI(db)

	r := gin.Default()

	r.GET("/products", productAPI.FindAll)
	r.GET("/products/:id", productAPI.FindByID)
	r.POST("/products", productAPI.Create)
	r.PUT("/products/:id", productAPI.Update)
	r.DELETE("/products/:id", productAPI.Delete)

	err := r.Run()
	if err != nil {
		panic(err)
	}
}

Build, run and test

You can type the following commands at the project root directory to build and run

  • Build the project with go build. It will generate an execute file rest-gin-gorm
  • Type the following command to run, replace root:123456 with your MySQL Server username and password, replace test with your database name
PORT=8080 DB_URL="root:123456@/test?charset=utf8&parseTime=True&loc=Local" ./rest-gin-gorm

You can test the APIs by using HTTPie command line

  • Find all products http localhost:8080/products
  • Find a product http localhost:8080/products/1
  • Create a new product http POST localhost:8080/products code=p1 price=10
  • Update a product http PUT localhost:8080/products/1 code=p1 price=100
  • Delete a product http DELETE localhost:8080/products/1

Conclusion

In this tutorial, we learned using Go Modules, Wire, Gin, and Gorm to build a CRUD RESTful APIs example. You can find the full source code at here

Follow HelloKoding