Getting Started Backend Development with GoLang

Getting Started Backend Development with GoLang

Learn the most popular Go framework Gin with MySQL

Oct 11, 2022ยท

8 min read

Play this article

Go, (also known as GoLang) is a compiled, statically typed, fast and high-performance programming language created by Google developers who were unhappy with the C, C++, Java and all the languages that failed to manage large network servers. Go is an extremely versatile lightweight language that can solve a lot of problems ranging from Web Backend, Cloud native software, SAAS products, IoT and Device drivers. Concurrency support in Go is amazing and it allows thousands of requests simultaneously without consuming too much RAM. Since Node.Js is single-threaded, sometimes a simple CPU-related program slows down your app. Probably GoLang doesn't have an easy learning curve as Node.Js.

In this article, I am going to talk more about one of the popular framework that is Gin Gonic and will show you how to develop a book management application along with MySQL. Before that, Let's understand some important concepts of Gin and how to use it.

  • Initialize you go using the following command inside your project directory
go mod init <MODULE PATH>

Example: img1.png

  • A go mod file will be created inside your root directory.

  • Download the gin web framework from https://gin-gonic.com/

go get -u github.com/gin-gonic/gin
  • Create a main.go file and import the gin package.
package main

import (
    "github.com/gin-gonic/gin"
)
  • Let's understand the very basic Hello World example of Gin
package main

import (
    "net/http"

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

func main() {
    var router = gin.Default()
    var PORT = ":5050"

    router.GET("/", func(ctx *gin.Context) {
        ctx.String(http.StatusOK, "Hello World")
    })

    router.Run(PORT)
}

gin.Default() will create a router with a default middleware where the path is relative and *gin.Context() is the handler function.

Middleware: Middleware is the function that has access to the request object and response object. It lies between the client and server to provide unified services.

router.GET() method will handle the GET request to our server. It contains two parameters. The first is the URL that will act. In this example, / is the root of our server. Another one is func(ctx *gin.Context){} that manages the flow of JSON request and JSON response.

router.RUN(PORT) will run the server on the PORT written. If you don't want hard coded PORT, you can use router.RUN(). Gin will run it on PORT 3000 by default. You can wrap it like log.Fatal(router.Run(PORT)). Fatal() function is used to print specified message with timestamps on the console screen.

  • Now let's run our first server made on Go and test it. The server will start listening on http://localhost:5050/
go run main.go

img2.png

But this go run main.go command will not reload the server again if you have made any changes in your file. If you did development with nodejs-based app, I am pretty sure you have used nodemon. Nodemon is a tool that automatically restarts the application when file changes are detected. Similarly, in golang, you can use cosmtrek/air for live reload of Go apps.

  • How to install it?
    go install github.com/cosmtrek/air@latest
    air init
    
    It will create a default .air.toml file in the current project directory. Now use air and run your Go server.
    air
    

img9.png

For more information, refer https://github.com/cosmtrek/air

  • There are several REST API functionality available on gin that you can use to manage your API calling.
  router.GET("/", getting)
  router.POST("/", posting)
  router.PUT("/", putting)
  router.DELETE("/", deleting)
  router.PATCH("/", patching)
  router.HEAD("/", head)
  router.OPTIONS("/", options)
  • Handle URL parameter You can handle the parameter provided on the URL by using ginContext
router.GET("/book/:id", func(ctx *gin.Context) {
    id := ctx.Param("id")

    ctx.String(http.StatusOK, id)
})

img3.png

You can manage queries using ctx.Query() method.

    // The request responds to matched URL : http://localhost:5050/book?name=ThinkAndGrowRich
    // ctx.DefaultQuery("name","Guest") can be used if no query provided

    router.GET("/book", func(ctx *gin.Context) {
        name := ctx.Query("name", "Rwitesh")
        ctx.String(http.StatusOK, name)
    })

img4.png

  • Model Binding and Validation

Let's create a User type struct that will be used to strictly check the fields you want to bind

type User struct {
    Name     string `form:"name" json:"name" binding:"required"`
    Password string `form:"password" json:"password" binding:"required"`
}
router.POST("/user", func(ctx *gin.Context) {
        var json User

        if err := ctx.ShouldBindJSON(&json); err == nil {
            if json.Name == "Rwitesh" && json.Password == "123456789" {
                ctx.JSON(http.StatusOK, gin.H{
                    "status": "Logged in successfully",
                })
            } else {
                ctx.JSON(http.StatusOK, gin.H{
                    "status": "Unauthorized",
                })
            }
        } else {
            ctx.JSON(http.StatusBadRequest, gin.H{"error": "No data found"})
        }
    })

You can check the following output to understand the concepts of how the validation is working.

img5.png

img6.png

img7.png

You can use custom validator for your request. Refer https://github.com/go-playground/validator

  • Serving Static Files Create an assets folder and index.html file inside assets.
    router.Static("/assets", "./assets")

img8.png

Even you can access static files using router.StaticFile("/home", "./assets/index.html") It can be accessed via http://localhost:5050/home

  • Another way to render static files
router.LoadHTMLGlob("./templates/*")  // Storing all the static HTML files in templates folder

router.GET("/home", func(ctx *gin.Context) {
        ctx.HTML(http.StatusOK, "home.html", nil)
}) // http://localhost:5050/home

router.GET("/contact", func(ctx *gin.Context) {
        ctx.HTML(http.StatusOK, "contact.html", nil)
}) // http://localhost:5050/contact
  • Custom Middleware You can use router.User() for middleware. Middleware is a special function that is executed on requests from a client to perfrom some operation before/after the server processes the request and serves the response back to the client.
router.Use(func(ctx *gin.Context) {
    // do something before a request
    ctx.Next() 
    // do something after the request
})

Connect your Go server with MySQL using GORM

GORM is an ORM library for Golang, which aims to be developer friendly. ORM means "Object-relational mapper" that automates the transfer of data from the database.

- config
    - db.go
- controllers
    - controller.go
- models
    - book.go
- routes
    - route.go

main.go
go.mod
go.sum
  • Install

    go get -u gorm.io/gorm
    go get -u gorm.io/driver/mysql
    
  • Import gorm and mysql package in config/db.go

    import (
    "gorm.io/gorm"
    "gorm.io/driver/mysql"
    )
    
  • Connect with the database

  • The entire config/db.go file will look like this which will help you to connect with the database
package config

import (
    "fmt"
    "log"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

var DB *gorm.DB

func GetDB() *gorm.DB {
    DB = ConnectDB()
    return DB
}

func ConnectDB() *gorm.DB {
    // dataSourceName := "username:password@tcp(hostname:port)/databaseName?charset=utf8&parseTime=true&loc=Local"
    dataSourceName := "rwitesh:rwitesh@tcp(127.0.0.1:3306)/bookdb?charset=utf8&parseTime=true&loc=Local"

    db, err := gorm.Open(mysql.Open(dataSourceName), &gorm.Config{})

    if err != nil {
        log.Println("Error connecting to database.")
        return nil
    }
    return db
}
  • Create models/book.go that will manage the data in the database
package models

import (
    "github.com/rwiteshbera/gin_golang/config"
    "gorm.io/gorm"
)

var db *gorm.DB

// Database model
type Book struct {
    gorm.Model
    Name        string `gorm:""json:"name"`
    Author      string `gorm:""json:"author"`
    Publication string `gorm:""json:"publication"`
}

func init() {
    db = config.GetDB()

    // Migrate the type Book schema to database
    db.AutoMigrate(&Book{})
}

func CreateBook(b *Book) *Book {
    // Create a new book in the database
    db.Create(&b)
    return b
}

func GetAllBooks() []Book {
    var Books []Book // Type slice Book
    db.Find(&Books)
    return Books
}

func GetBookById(Id int64) (*Book, *gorm.DB) {
    var getBook Book
    db := db.Where("ID=?", Id).Find(&getBook)
    return &getBook, db
}

func DeleteBook(Id int64) Book {
    var book Book
    db.Where("ID=?", Id).Delete(&book)
    return book
}
  • Create a controllers/controller.go where all POST, GET, UPDATE, and DELETE methods will be given. It will handle all the requests and responses from client or postman or any API testing tool.
package controllers

import (
    "fmt"
    "strconv"

    "github.com/gin-gonic/gin"
    "github.com/rwiteshbera/gin_golang/models"
)

var NewBook models.Book

func GetBook(ctx *gin.Context) {
    newBooks := models.GetAllBooks() // Invoking GetAllBooks function from models package
    ctx.JSON(200, newBooks)
}

func GetBookById(ctx *gin.Context) {
    Id := ctx.Param("id")
    id, err := strconv.ParseInt(Id, 10, 64) // Parsing string type to int64
    if err != nil {
        fmt.Println("Error while parsing url")
    }
    bookDetails, _ := models.GetBookById(id) // Invoking GetBookId function from models package

    //ctx.JSON() will return data in JSON format. It Sets a JSON response body by appending a Content-Type: application/json header on the response.
    ctx.JSON(200, bookDetails)
}

func CreateBook(ctx *gin.Context) {
    bookData := &models.Book{}

    // Parsing request data is a crucial part of a web application. In this example, a struct type Book tells the binder to bind request data to its corresponding key provided inside models.Book
    ctx.Bind(&bookData)
    createBook := models.CreateBook(bookData) // Invoking CreateBook function from models package
    ctx.JSON(200, createBook)
}

func UpdateBook(ctx *gin.Context) {
    updatedBook := &models.Book{}


    // Parsing request data is a crucial part of a web application. In this example, a struct type Book tells the binder to bind request data to its corresponding key provided inside models.Book
    ctx.Bind(&updatedBook) 

    Id := ctx.Param("id")
    id, err := strconv.ParseInt(Id, 10, 64) // Parsing string type to int64
    if err != nil {
        fmt.Println("Error while parsing url")
    }
    bookDetails, db := models.GetBookById(id) // Invoking GetBookById function from models package

    if updatedBook.Name != "" {
        bookDetails.Name = updatedBook.Name
    }
    if updatedBook.Author != "" {
        bookDetails.Author = updatedBook.Author
    }
    if updatedBook.Publication != "" {
        bookDetails.Publication = updatedBook.Publication
    }
    db.Save(&bookDetails)
    ctx.JSON(200, bookDetails)
}

func DeleteBook(ctx *gin.Context) {
    // Param is something I have explained already. The URL parameter is a way to pass information by its URL.
    // Here we are grabbing 'id' from the URL
    Id := ctx.Param("id") 

    // We have to parse it from string into int64 string. Otherwise, the database will not be able to recognize it.
    id, err := strconv.ParseInt(Id, 10, 64) // Parsing string type to int64
    if err != nil {
        fmt.Println("Error while parsing url")
    }
    deletedBook := models.DeleteBook(id) // Invoking DeleteBook function from models package
    ctx.JSON(200, deletedBook)
}
  • Create routes/route.go where all the routes will be defined. You have to import the controller functions here to manage the requests and responses.
package routes

import (
    "github.com/gin-gonic/gin"
    "github.com/rwiteshbera/gin_golang/controllers"
)

var BookStoreRoutes = func(router *gin.Engine) {
    router.GET("/book", controllers.GetBook) // Get all the books
    router.GET("/book/:id", controllers.GetBookById) // Get book details by Id
    router.POST("/book", controllers.CreateBook) // Create a new book by providing details in json format
    router.PUT("/book/:id", controllers.UpdateBook) // Update book details
    router.DELETE("/book/:id", controllers.DeleteBook) // Delete a book by Id
}

and the main.go file will look like this

package main

import (
    "log"
    "github.com/gin-gonic/gin"
    "github.com/rwiteshbera/gin_golang/routes"
)

func main() {
    var r = gin.Default()
    var PORT = ":5050"

    routes.BookStoreRoutes(r) // Importing all the routes that are defined inside "routes/route.go"

    log.Fatal(r.Run(PORT))
}
  • Now, it's time to test our API.
  • Create a book img10.png

Let me add some more books.

  • Get all the books img11.png

  • Get Book by Id img12.png

  • Delete Book by Id Make a DELETE request with http://localhost:5050/book/{Id} and it will be the specified book.

Thank you for reading this article. Hope this article is useful for you. You can find the source code here on my Github. Do not forget to give it a star โญ. Happy Coding ๐Ÿ˜‡

ย