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).
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
}
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
}
time.Time
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.
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.
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"})
}
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.