Getting Started Backend Development with GoLang
Learn the most popular Go framework Gin with MySQL
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:
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
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?
It will create a defaultgo install github.com/cosmtrek/air@latest air init
.air.toml
file in the current project directory. Now useair
and run your Go server.air
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)
})
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)
})
- 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.
You can use custom validator for your request. Refer https://github.com/go-playground/validator
- Serving Static Files
Create an
assets
folder andindex.html
file inside assets.
router.Static("/assets", "./assets")
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
andmysql
package inconfig/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 thecontroller
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
Let me add some more books.
Get all the books
Get Book by Id
Delete Book by Id Make a
DELETE
request withhttp://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 ๐