Go with the Gin Framework
We’re Earthly. We make building software simpler and therefore faster using containerization. If you’re using Go, Earthly can help streamline your build process. Check it out.
Go is increasing in popularity for many reasons, from speed to ease of use and so much more. The Go standard library has most of the functionalities you’ll need to build web applications in the net/http
package. There are many web-based packages in the Go ecosystem to build fast web applications.
The Gin framework is one of the popular web packages in the Go ecosystem for building web applications. Gin provides most of the functionalities you’ll need in a web framework featuring a martini-like API with high performance and easy error management.
Gin is extendable, and the package provides built-in rendering support for HTML, JSON, and XML rendering with documentation support, among many other features.
This tutorial will walk you through developing web applications in Go with the Gin framework. You’ll learn how to use Gin by building a CRUD API. You’ll use the Gin framework for routing, JSON parsing, request-response-related operations, and the GORM package for the database (SQLite) auto migrations and operations.
Note: You can find the complete code for this tutorial on this GitHub Gist.
Prerequisites
You’ll need to meet a few prerequisites to understand and follow this hands-on tutorial:
- You have experience working with Go and Go installed on your machine.
- Experience working with the GORM package and SQL databases in Go is a plus.
Getting Started With Gin and GORM
Once you’ve set up your Go workspace, install the gin
package in your working directory using this command.
go get github.com/gin-gonic/gin
You’ll also need to install the gorm
package and the gorm
sqlite driver for connecting to the SQLite database.
Run these commands in your working directory to install the packages:
go get gorm.io/gorm
go get gorm.io/driver/sqlite
These are the imports you’ll need for this tutorial:
import (
"github.com/gin-gonic/gin"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"log"
"net/http"
)
You’ll use the log
package for logging-related operations and the http
package for starting a server and other operations.
Setting Up GORM and SQLite for Persistence
GORM uses structs for the database model. You can declare the struct with constraint tags and set up auto migrations for the struct.
Here’s an example company
struct with gorm
and json
tags:
type Companies struct {
string `gorm:"primary_key" json:"name"`
Name int `json:"created"`
Created string `json:"product"`
Product }
The Name
field has the primary key constraint, and the constraints would reflect on table creation.
On database migrations, GORM creates a table that matches the struct model. Here’s an example of a database table matching the struct model after a series of POST requests.
You can declare a function to manage database connections and auto migrations.
func DBConnection() (*gorm.DB, error) {
"test.db"), &gorm.Config{})
db, err := gorm.Open(sqlite.Open(if err != nil {
return db, err
}
err = db.AutoMigrate(&Companies{})if err != nil {
return nil, err
}
return db, nil
}
The DBConnection
function returns a GORM database instance *gorm.DB
and an error. The Open
method returns a database connection after creating a connection with the Open
method of the sqlite
driver package.
The AutoMigrate
method helps with auto-migrating the Companies
struct.
Setting Up Handler Functions With Gin
Handler functions will house your business logic based on the input and response of your API. In this tutorial, you’ll learn how to implement the CRUD handler functions for your API.
A typical handler gin
handler function takes in the gin
context struct.
func GetCompany(ctx *gin.Context) {
}
Most of the functionalities you’ll need in your handler function are methods of the Context
struct.
Mounting Handlers and Setting Up a Server
You’ll need to mount the handlers, define the routes and their respective handler functions, then start the server before interacting with your API endpoints.
You can use the Default
method to create a gin
router instance. The Default
method returns a router instance.
func main() {
router := Gin.Default()":8080"))
log.Fatal(router.Run( }
After creating the router instance, you can use the GET
, POST
, PUT
, and DELETE
methods to define routes and their respective handler functions.
The Run
method of your router instance starts a server to run on the specified port. You can now define your handler functions and mount them in the main function.
The POST
Request Handler
The POST
request handler function will accept JSON input from the client for GORM to migrate to the decoded JSON struct database:
func PostCompany(ctx *gin.Context) {
var company Companies
if err := ctx.ShouldBindJSON(&company); err != nil {
"error": err.Error()})
ctx.JSON(http.StatusBadRequest, gin.H{return
}
newCompany := Companies{
Name: company.Name,
Created: company.Created,
Product: company.Product,
}
db, err := DBConnection()if err != nil {
log.Println(err)
}if err := db.Create(&newCompany).Error; err != nil {
ctx.JSON(http.StatusInternalServerError, \"error": "Database Migration Error"})
gin.H{
}
ctx.JSON(http.StatusOK, company)
}
The PostCompany
handler function receives POST requests from the server, parses the JSON into the newCompany
struct, and the Create
method of your database instance creates a new row of the struct inputs in the database.
If there’s an error decoding the JSON request body or migrating the data, the handler function returns the JSON from the error handling if
statement.
You can mount the PostCompany
handler function and assign a route to the handler function with the POST
method of your writer instance that takes in the route string and the handler function.
func main() {
router := Gin.Default()"/company", PostCompany)
router.POST(":8080"))
log.Fatal(router.Run( }
Here’s a CURL request that tests the PostCompany
handler function:
curl -X POST -H "Content-Type: application/json" \
-d '{"name": "TestCompany", "created": "2021-01-01", \
"product": "TestProduct"}' "http://localhost:8080/api/v1/company"
The CURL request sends a POST request to the /company
endpoint with a JSON payload containing fields that match the Companies
struct.
The GET
Request Handler
The GET
request will accept a parameter (the company name) from the client and return a JSON response to the client from the Companies
database.
func GetCompany(ctx *gin.Context) {
var company Companies
"company")
name := ctx.Param(
db, err := DBConnection()if err != nil {
log.Println(err)
}if err := db.Where("name= ?", name).First(&company).Error; err != nil {
"Failed": "Company not found"})
ctx.JSON(http.StatusNotFound, gin.H{return
}
ctx.JSON(http.StatusOK, company)
}
The GetCompany
handler function retrieves the company
name from the request using the Param
method of the context instance, creates a database connection, and retrieves the row where the Name
field equals the name from the request.
You can mount the GetCompany
handler function with the GET
method of your router instance. The GET
method, just like the POST
method, takes in the route and the handler function as parameters
func main() {
router := Gin.Default()"api/v1/:company", GetCompany)
router.GET("/company", PostCompany)
router.POST(
":8080"))
log.Fatal(router.Run( }
Here’s the CURL request for the GetCompany
handler function. The CURL
request sends a request to the /api/v1/:company
route with the data attached to the URL:
curl -X GET "http://localhost:8080/api/v1/TestCompany"
"name": "TestCompany", "created": "2021-01-01", "product": "TestProduct"} {
The PUT
Request Handler
PUT
request handlers are responsible for the update operations. The PUT
request will receive a parameter and a JSON request body from the client and search the database before updating the database entry.
func UpdateCompany(ctx *gin.Context) {
var company Companies
"company")
name := ctx.Param(
db, err := DBConnection()if err != nil {
log.Println(err)
}
if err := db.Where("company = ?", name).First(&company).Error; \
nil {
err !=
ctx.JSON(http.StatusNotFound, \"error": "Company doesn't exist "})
gin.H{return
}
if err := ctx.ShouldBindJSON(&company); \
nil {
err !=
ctx.JSON(http.StatusBadRequest, \"error": err.Error()})
gin.H{return
}
if err := db.Model(&company).Updates(Companies{
Name: company.Name,
Created: company.Created,
Product: company.Product,nil {
}).Error; err !=
ctx.JSON(http.StatusInternalServerError, \"error": err.Error()})
gin.H{return
}
ctx.JSON(http.StatusOK, company)
}
The UpdateCompany
handler function retrieves the company name from the request with the Param
method and updates the row with the JSON from the request with the Updates
method of your database instance.
Similar to the POST and GET handler functions, you can mount the UpdateCompany
handler function with the PUT
method of the router instance.
func main() {
router := Gin.Default()"api/v1/:company", GetCompany)
router.GET("/company", PostCompany)
router.POST("api/v1/:company", UpdateCompany)
router.PUT(
":8080"))
log.Fatal(router.Run( }
Here’s the CURL request that tests the UpdateCompany
handler function. Insert a company name in the specified field to run the CURL request effectively:
curl -X PUT -H "Content-Type: application/json" \
-d '{"name": "TestCompany", "created": "2022-01-01", \
"product": "UpdatedProduct"}' "http://localhost:8080/api/v1/<company_name>"
"name": "TestCompany", "created": "2022-01-01", \
{"product": "UpdatedProduct "}
The CURL request sends a PUT request to the api/v1/:company
endpoint with a JSON payload as the replacement for the update operation.
The DELETE
Request Handler
Your DELETE
request handler will receive a parameter from the client’s request and search through the database for the valid entry to delete the row with the access.
func DeleteCompany(ctx *gin.Context) {
var company Companies
"company")
name := ctx.Param(
db, err := DBConnection()if err != nil {
log.Println(err)
}
if err := db.Where("company = ?", name).First(&company).Error; \
nil {
err != "error": "company not found!"})
ctx.JSON(http.StatusNotFound, gin.H{return
}
if err := db.Delete(&company).Error; err != nil {
ctx.JSON(http.StatusInternalServerError, \"error": err.Error()})
gin.H{return
}"message": "Company Deleted"})
ctx.JSON(http.StatusOK, gin.H{
}
The DeleteCompany
handler function retrieves the company name from the request, creates a database connection, searches for the company with the Where
method of the database instance, and deletes the row from the database. After a valid request, the DeleteCompany
handler function returns a message and the 200 HTTP status code to the client.
You can mount the DeleteCompany
handler function with the DELETE
method.
func main() {
router := Gin.Default()"api/v1/:company", GetCompany)
router.GET("/company", PostCompany)
router.POST("api/v1/:company", UpdateCompany)
router.PUT("api/v1/:company", DeleteCompany)
router.DELETE(
":8080"))
log.Fatal(router.Run( }
Here’s the CURL request for the DeleteCompany
handler function. Insert a company name in the specified field to run the CURL request effectively:
curl -X DELETE "http://localhost:8080/api/v1/<company_name>"
"message": "Company Deleted"% } {
The CURL request sends a DELETE request to the api/v1/:company
endpoint to delete the matching row from the database.
Conclusion
This tutorial guided you through building web applications using the Gin Framework, including creating an API with CRUD functionalities. To further your skills, explore the Gin documentation. It will help you implement more complex functionalities for your apps.
And as you continue to develop your web applications, consider taking your build process up a notch with Earthly. It’s an efficient tool for creating reproducible builds, which can be a great addition to your development toolkit.
Enjoyed using Gin for web development? You’ll likely appreciate what Earthly has to offer. Check it out!
Earthly makes CI/CD super simple
Fast, repeatable CI/CD with an instantly familiar syntax – like Dockerfile and Makefile had a baby.