Golang: gorm with MySQL and gin

Explore how to use Gorm with the Gin framework(Golang)
Nov 24 2020 · 5 min read

Introduction 

Golang is a relatively young and “future-proof” language. It is the prettiest programming language in the world nowadays, it’s simple but provides high performance though. There are many benefits of using Golang over languages like PHP, Java, and RoR. It’s famous among programmers for its amazing scalability and concurrency features.

Gin is a high-performance micro-framework that delivers a very minimalistic framework that carries with it only the most essential features, libraries, and functionalities needed to build web applications and microservices.

Gin makes it simple to build a request-handling pipeline from modular, reusable pieces by allowing you to write middleware that can be plugged into one or more request handlers or groups of request handlers.

I’m writing this article to describe how to use ORM(Object Relational Mapping) in Golang with gin. For that Go is providing a package named gorm. So, let's get to dive into it...

We are what we repeatedly do. Excellence, then, is not an act, but a habit. Try out Justly and start building your habits today!

 

To run gorm with MySQL we need the below packages, so kindly add them to the project.
1. github.com/gin-gonic/gin
2. gorm.io/gorm
3. gorm.io/driver/mysql

Yay! We are done with basic package information, let’s go for code.

Let’s create a project named gorm-test (anything you want).

Set up entry point of app

main.go

package main

import (
   "github.com/gin-gonic/gin"
   "gorm-test/controllers"
   "net/http"
)

func main() {
   r := setupRouter()
   _ = r.Run(":8080")
}

func setupRouter() *gin.Engine {
   r := gin.Default()

   r.GET("ping", func(c *gin.Context) {
      c.JSON(http.StatusOK, "pong")
   })

   return r
}

Setup database

database/db.go

package database

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

const DB_USERNAME = "root"
const DB_PASSWORD = "root"
const DB_NAME = "my_db"
const DB_HOST = "127.0.0.1"
const DB_PORT = "3306"

var Db *gorm.DB
func InitDb() *gorm.DB {
	Db = connectDB()
	return Db
}

func connectDB() (*gorm.DB) {
	var err error
	dsn := DB_USERNAME +":"+ DB_PASSWORD +"@tcp"+ "(" + DB_HOST + ":" + DB_PORT +")/" + DB_NAME + "?" + "parseTime=true&loc=Local"
	fmt.Println("dsn : ", dsn)
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

	if err != nil {
		fmt.Println("Error connecting to database : error=%v", err)
		return nil
	}

	return db
}
  • parseTime=true will scan DATE and DATETIME automatically to time.Time
  • loc=Local sets system’s timezone, you can set required timezone instead of Local.

Now, It’s time to get in touch with Tables and Schema, also how migration works with gorm.

Create models/user.go and paste the below content into it, we are using a very basic example here, by doing CRUD operations in the users table.

Add Models

models/user.go

package models

import (
	"gorm.io/gorm"
)

type User struct {
	gorm.Model
	ID    int
	Name  string
	Email string
}

//create a user
func CreateUser(db *gorm.DB, User *User) (err error) {
	err = db.Create(User).Error
	if err != nil {
		return err
	}
	return nil
}

//get users
func GetUsers(db *gorm.DB, User *[]User) (err error) {
	err = db.Find(User).Error
	if err != nil {
		return err
	}
	return nil
}

//get user by id
func GetUser(db *gorm.DB, User *User, id int) (err error) {
	err = db.Where("id = ?", id).First(User).Error
	if err != nil {
		return err
	}
	return nil
}

//update user
func UpdateUser(db *gorm.DB, User *User) (err error) {
	db.Save(User)
	return nil
}

//delete user
func DeleteUser(db *gorm.DB, User *User, id int) (err error) {
	db.Where("id = ?", id).Delete(User)
	return nil
}

The user model will auto-add the following three fields on migration:

CreatedAt — used to store records created time

UpdatedAt — used to store records updated time

DeletedAt — used to store records deleted time, It won’t delete the records just set the value of DeletedAt’s field to the current time and you won’t find the record when querying i.e. what we call soft deletion.

One of the greatest advantages of using ORM is we don’t need to write raw queries or complex schema relations. It can be handled efficiently using ORM, for us, gorm. You can find more about gorm methods here.

Now, let’s add a controller to deal with the model.

Add repository

controllers/user.go

package controllers

import (
	"errors"
	"gorm-test/database"
	"gorm-test/models"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

type UserRepo struct {
	Db *gorm.DB
}

func New() *UserRepo {
	db := database.InitDb()
	db.AutoMigrate(&models.User{})
	return &UserRepo{Db: db}
}

//create user
func (repository *UserRepo) CreateUser(c *gin.Context) {
	var user models.User
	c.BindJSON(&user)
	err := models.CreateUser(repository.Db, &user)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err})
		return
	}
	c.JSON(http.StatusOK, user)
}

//get users
func (repository *UserRepo) GetUsers(c *gin.Context) {
	var user []models.User
	err := models.GetUsers(repository.Db, &user)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err})
		return
	}
	c.JSON(http.StatusOK, user)
}

//get user by id
func (repository *UserRepo) GetUser(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	var user models.User
	err := models.GetUser(repository.Db, &user, id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			c.AbortWithStatus(http.StatusNotFound)
			return
		}

		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err})
		return
	}
	c.JSON(http.StatusOK, user)
}

// update user
func (repository *UserRepo) UpdateUser(c *gin.Context) {
	var user models.User
	id, _ := strconv.Atoi(c.Param("id"))
	err := models.GetUser(repository.Db, &user, id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			c.AbortWithStatus(http.StatusNotFound)
			return
		}

		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err})
		return
	}
	c.BindJSON(&user)
	err = models.UpdateUser(repository.Db, &user)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err})
		return
	}
	c.JSON(http.StatusOK, user)
}

// delete user
func (repository *UserRepo) DeleteUser(c *gin.Context) {
	var user models.User
	id, _ := strconv.Atoi(c.Param("id"))
	err := models.DeleteUser(repository.Db, &user, id)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err})
		return
	}
	c.JSON(http.StatusOK, gin.H{"message": "User deleted successfully"})
}
  • db.AutoMigrate() automatically migrates our schema, to keep our schema upto date.
  • Remember that AutoMigrate() will only create tables, fix missing columns and missing indexes, and won’t manipulate data or type of existing column.

The controller will invoke respective model methods. What I love is, gorm auto handles soft deletes. You can learn more about the types of deletions provided by Gorm.

Let’s add routes to hit controller methods. In main.go add the following lines of code.

userRepo := controllers.New()
r.POST("/users", userRepo.CreateUser)
r.GET("/users", userRepo.GetUsers)
r.GET("/users/:id", userRepo.GetUser)
r.PUT("/users/:id", userRepo.UpdateUser)
r.DELETE("/users/:id", userRepo.DeleteUser)

final main.go will look like this :

package main

import (
	"github.com/gin-gonic/gin"
	"gorm-test/controllers"
	"net/http"
)

func main() {
	r := setupRouter()
	_ = r.Run(":8080")
}

func setupRouter() *gin.Engine {
	r := gin.Default()

	r.GET("ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, "pong")
	})

	userRepo := controllers.New()
	r.POST("/users", userRepo.CreateUser)
	r.GET("/users", userRepo.GetUsers)
	r.GET("/users/:id", userRepo.GetUser)
	r.PUT("/users/:id", userRepo.UpdateUser)
	r.DELETE("/users/:id", userRepo.DeleteUser)

	return r
}

Let’s build the project and you are ready to go…!!

You will find a server running on port 8080. You can test CRUD operations with the above-defined API endpoints.

Hope it helped you to get basic interaction with Gorm and gin with MySQL.

Check out the full source code on GitHub.


nidhi-d image
Nidhi Davra
Web developer@canopas | Gravitated towards Web | Eager to assist


nidhi-d image
Nidhi Davra
Web developer@canopas | Gravitated towards Web | Eager to assist

contact-footer
Say Hello!
footer
Subscribe Here!
Follow us on
2024 Canopas Software LLP. All rights reserved.