Since I am also a beginner of Golang, this tutorial will stay on the level of using GIN without analyzing its implementation details. I will have the opportunity to further study it later.

Gin is currently one of golang’s main Web frameworks. We chose this framework because of its efficient routing performance and long-term maintenance. Currently, the number of stars on Github has exceeded 3W. This tutorial is suitable for beginners on the road.

How to install

The installation of GO is skipped here and gin is directly installed. This article uses Govendor (management package dependency tool) to install gin:

  1. Install govendor
go get github.com/kardianos/govendor
Copy the code
  1. Create the project directory under gopath and enter
mkdir $GOPATH/src/myapp && cd $_
Copy the code
  1. Initialize the project with govendor and pull gin
govendor init
govendor fetch github.com/gin-gonic/gin
Copy the code

Now that the installation is complete, we can see that there is a vendor directory in the project, and this directory is the dependency required by the project.

Basic usage

Gin’s syntax is simple enough that you can see it at a sight. The following example creates a GIN Router using the default middleware (Logger and Recovery). A route listener for the “/ping” request is then created. Finally, the service starts, listening on port 8080 by default.

// main.go
package main

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

func main(a) {
	r := gin.Default() // Use default middleware (Logger and recovery)
	r.GET("/ping".func(c *gin.Context) {
    c.JSON(200, gin.H{ // Return a JSON with a status code of 200. Gin.H is short for map[string]interface{}
			"message": "pong",
		})
	})
	r.Run() // Start the service and listen on port 8080 by default
}
Copy the code

Execute command:

go run main.go
Copy the code

Routing management

Basic usage

The request methods include GET, POST, patch, delete, and options. There is also any, which means that any request method will listen.

func main(a) {
	router := gin.Default()

	router.GET("/someGet", handle)
	router.POST("/somePost", handle)
	router.PUT("/somePut", handle)
	router.DELETE("/someDelete", handle)
	router.PATCH("/somePatch", handle)
	router.HEAD("/someHead", handle)
	router.OPTIONS("/someOptions", handle)
    
    router.ANY("/any", handle)
    
	router.Run()
}

func handle(context *gin.Context) {
	context.String(http.StatusOK, "hello world")}Copy the code

Packet routing can be done through router.Group:

func main(a) {
	router := gin.Default()

	v1 := router.Group("/v1")
	{
		v1.POST("/login", loginEndpoint)
		v1.POST("/submit", submitEndpoint)
		v1.POST("/read", readEndpoint)
	}

	v2 := router.Group("/v2")
	{
		v2.POST("/login", loginEndpoint)
		v2.POST("/submit", submitEndpoint)
		v2.POST("/read", readEndpoint)
	}

	router.Run(": 8080")}Copy the code

Request Parameter acquisition

Parameters in path

func main(a) {
	router := gin.Default()

    / / match/user/John
	router.GET("/user/:name".func(c *gin.Context) {
		name := c.Param("name")
		c.String(http.StatusOK, "Hello %s", name)
	})

    // Matches /user/ John/and /user/ John /send
	router.GET("/user/:name/*action".func(c *gin.Context) {
		name := c.Param("name")
		action := c.Param("action")
		message := name + " is " + action
		c.String(http.StatusOK, message)
	})

	router.Run(": 8080")}Copy the code

Parameters in query

func main(a) {
	router := gin.Default()

	// welcome? firstname=Jane&lastname=Doe
    router.GET("/user".func(c *gin.Context) {
		firstname := c.DefaultQuery("name"."kim") // Get the name in query, or Kim if not
		lastname := c.Query("age") // Get the age in query

		c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
	})
	router.Run(": 8080")}Copy the code

Parameters in multipart/urlencoded form

func main(a) {
	router := gin.Default()

	router.POST("/form_post".func(c *gin.Context) {
		message := c.PostForm("age")
		nick := c.DefaultPostForm("name"."kim")

		c.JSON(200, gin.H{
			"status":  "posted"."message": message,
			"nick":    nick,
		})
	})
	router.Run(": 8080")}Copy the code
curl http://127.0.0.1:8080 -X POST -d 'name=john&age=25'
Copy the code

Model binding and parameter validation

Basic usage

We have seen x-www-form-urlencoded parameter processing, and now more and more applications are used to JSON communication, that is, whether a response is returned or a request is submitted, The content-Type is in application/ JSON format. Some old Web form pages were x-www-form-urlencoded, which required our server to be able to handle multiple Content-Type parameters. Because GO is a static language, the data model needs to be defined first, which requires gin’s Model Bind functionality.

Gin validates the parameters using Go-playground /validator.v8 to see the full documentation.

You need to set the tag on the bound field. For example, if the binding format is JSON, set json to “fieldName”. Gin also provides two sets of binding methods:

  • Must bind
    • Methods – Bind.BindJSON.BindXML.BindQuery.BindYAML
    • Behavior – These methods are used at the bottomMustBindWithIf there is a binding error, the request will be aborted by the following commandc.AbortWithError(400, err).SetType(ErrorTypeBind), the response status code will be set to 400, the request headerContent-TypeIs set totext/plain; charset=utf-8. Note that if you try to set up the response code after this, a warning will be issued[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422If you wish to have more control over your behavior, useShouldBindRelated methods
  • Should bind
    • Methods – ShouldBind.ShouldBindJSON.ShouldBindXML.ShouldBindQuery.ShouldBindYAML
    • Behavior – The underlying use of these methods is ShouldBindWith, which returns an error if there is a binding error and the developer can handle the request and error correctly.

When we use a binding method, Gin will infer which binder to use based on the Content-Type, and if you’re sure what you’re binding to, you can either use MustBindWith or BindingWith. You can also specify a rule specific modifier for a field. If a field is decorated with binding:”required” and its value is null at binding time, an error will be returned.

package main

import (
	"net/http"

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

type Person struct {
	Name string `json:"name" binding:"required"` // The value is in json format and must be name
	Age  int    `json:"age" binding:"required,gt=20"` // The value is in the json format. The value is mandatory and must be greater than 20
}

func main(a) {

	router := gin.Default()

	router.POST("/test".func(context *gin.Context) {
		var person Person
        // here I'm sure it must be JSON so ShouldBindJSON, otherwise ShouldBind
		iferr := context.ShouldBindJSON(&person); err ! =nil { 
			context.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}
		context.JSON(http.StatusOK, gin.H{
			"success": true,
		})
	})

	router.Run(": 3000")}Copy the code
curl http://localhost:3000/test -X POST -d '{"name":"kim","age": 10}'
Copy the code

Custom validators

package main

import (
	"net/http"
	"reflect"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"gopkg.in/go-playground/validator.v8"
)

type Booking struct {
  // The validation method here is bookableDate
	CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
	// gtfield=CheckIn indicates that the greater field is CheckIn
  CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}

func bookableDate(
	v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
	field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string.) bool {
  // There are two things here, mapping and assertions
  // In this case, field is an Interface Type variable of reflect.Type. Use the Interface method to get the actual Type of the field Interface Type variable, which can be understood as the inverse of reflect.Value
  // In this case, the assertion is to convert a variable of interface type to time. time, provided that the latter implements the former interface
  // Field is a type cast
	if date, ok := field.Interface().(time.Time); ok {
		today := time.Now()
		if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
			return false}}return true
}

func main(a) {
	route := gin.Default()

  // Register custom validators
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		v.RegisterValidation("bookabledate", bookableDate)
	}

	route.GET("/bookable", getBookable)
	route.Run(": 8085")}func getBookable(c *gin.Context) {
	var b Booking
	if err := c.ShouldBindWith(&b, binding.Query); err == nil {
		c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})}else {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	}
}
Copy the code