Golang: gorm with MySQL and gin. Golang is a relatively young and…
By: Nidhi Canopas
Tags: MySQL, Golang, Orm, Gin, Gorm

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. It makes it simple to build a request handling pipeline from modular, reusable pieces. It does this 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...

Photo by Kai Wenzel on Unsplash

To run gorm with MySQL we need below packages, so kindly add them into the project.

1. github.com/gin-gonic/gin

2. gorm.io/gorm

3. gorm.io/driver/mysql

As we are going to use MySQL database we need a package for it. Yay ! we are done with basic package information, let’s go for code.

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

}

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"



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 a 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.

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 string) (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 string) (err error) {

db.Where("id = ?", id).Delete(User)

return nil

}

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, complex schema relations can be handled efficiently using ORM, gorm does the same job. You can find more about gorm methods here.

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

controllers/user.go

package controllers



import (

"errors"

"github.com/gin-gonic/gin"

"gorm-test/database"

"gorm-test/models"

"gorm.io/gorm"

"net/http"

)



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, _ := c.Params.Get("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, _ := c.Params.Get("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, _ := c.Params.Get("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 update to date. Keep in mind 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. One mostly liked thing is gorm auto handles soft deletes, You can learn more about types of deletion provided by gorm.

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

r.POST("/users", controllers.CreateUser)

r.GET("/users", controllers.GetUsers)

r.GET("/users/:id", controllers.GetUser)

r.PUT("/users/:id", controllers.UpdateUser)

r.DELETE("/users/:id", controllers.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 check CRUD operations with above-defined API endpoints. Hope it helped you to get basic interaction with gorm and gin with MySQL.

Full demonstration code available here. Happy coding!