Gin binding in Go: A tutorial with examples

Over the past few years, Go has become very popular for microservices. Gin is a web framework for Go that focuses on performance and productivity and features Martini-like APIs.

In this tutorial, we’ll show you how to use Gin’s binding. We’ll walk you through request payload validation, writing custom validation using reflection and the validator module, and building custom bindings for various formats, such as TOML, etc.

What is Gin binding?

Gin binding is an awesome serialization library. It supports JSON, XML, query parameter, and more out of the box and comes with a built-in validation framework.

Gin bindings are used to serialize JSON, XML, path parameters, form data, etc. to structs and maps. It also has a baked-in validation framework with complex validations.

Gin supports various formats by providing struct tags. For example, the uri tag is used to serialize path parameters:

package main

import (
   "fmt"
   "github.com/gin-gonic/gin"
   "net/http"
)

type Body struct {
  // json tag to serialize json body
   Name string `json:"name"`
}

func main() {
   engine:=gin.New()
   engine.POST("/test", func(context *gin.Context) {
      body:=Body{}
      // using BindJson method to serialize body with struct
      if err:=context.BindJSON(&body);err!=nil{
         context.AbortWithError(http.StatusBadRequest,err)
         return
      }
      fmt.Println(body)
      context.JSON(http.StatusAccepted,&body)
   })
   engine.Run(":3000")
}

BindJSON reads the body buffer to serialize it to a struct. BindJSON cannot be called on the same context twice because it flushes the body buffer.

If you want to serialize the body to two different structs, use ShouldBindBodyWith to copy the body buffer and add it to context.

if err:=context.ShouldBindBodyWith(&body,binding.JSON);err!=nil{
   context.AbortWithError(http.StatusBadRequest,err)
   return
}

In the same way, the XML body and path parameters are mapped to structs.

package main

import (
   "fmt"
   "github.com/gin-gonic/gin"
   "net/http"
)

// path paramter with name details will mapped to Details
type URI struct {
   Details string `json:"name" uri:"details"`
}

func main() {
   engine:=gin.New()
// adding path params to router
   engine.GET("/test/:details", func(context *gin.Context) {
      uri:=URI{}
      // binding to URI
      if err:=context.BindUri(&uri);err!=nil{
         context.AbortWithError(http.StatusBadRequest,err)
         return
      }
      fmt.Println(uri)
      context.JSON(http.StatusAccepted,&uri)
   })
   engine.Run(":3000")
}

The example above is a basic use case for binding to serialize the body, query, and path parameters.

Basic validation using Gin

Gin uses the validator package internally for validations. This package validator provides an extensive set of inbuilt validations, including required, type validation, and string validation.

Validations are added to structs:

type URI struct {
   Details string `json:"name" uri:"details" binding:"required"`
}

The validator package also supports more complex validation, such as len ,max, and min. Nested structs and arrays are also validated recursively.

type User struct {
   Name string `json:"name" binding:"required,min=3"`
   Age uint `json:"age" binding:"required,min=18"`
   Comments []*Comment `json:"comments" binding:"required"`
}

type Comment struct {
   Text string `json:"text" binding:"required,max=255"`
   Type string `json:"type" binding:"required,oneof=post nested"`
}

Gin comes with many inbuilt validations; you can find an exhaustive list on GitHub.

Writing custom validations

Not all use cases are well-suited to built-in Gin validations. For this reason, Gin provides methods to add custom validations.

The reflect package is used during the validation process to figure out types and the value of struct fields at runtime.

To create a new binding, you have to register a validation with a function that performs the validation.

 // getting the validation engine and type casting it.
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
   // registering validation for nontoneof
   v.RegisterValidation("notoneof", func(fl validator.FieldLevel) bool {
     // split values using ` `. eg. notoneof=bob rob job
      match:=strings.Split(fl.Param()," ")
     // convert field value to string
      value:=fl.Field().String()
      for _,s:=range match {
       // match value with struct filed tag
         if s==value {
            return false
         }
      }
      return true
   })
}

You can access the validation engine using the binding package for adding custom validators. The Validator variable is exported. Validator provides the Engine method, which returns the validation engine.

The RegisterValidation method on the engine takes a name and function that returns whether the field is valid or not.

You can access arameters passed to the validator using the Param method.

The Field method returns the value of the field in a struct. The value can be typecast to various data types.

validatoe.FieldLevel has access to a whole struct. You can also access different keys of a parent struct.

Accessing other struct fields

FieldLevel has a Top method that returns a reflect.Value type of the struct. That can be used to access the field in a struct.

For example, you can create a validation where two fields can’t have the same value using reflect.Value.

v.RegisterValidation("unique", func(fl validator.FieldLevel) bool {
  // get the fields which need to be unique
   match:=strings.Split(fl.Param()," ")
  // value of the field
   value:=fl.Field().String()
   for _,s:=range match {
     // access to struct and getting value by field name
      fs:=fl.Top().FieldByName(s)
      // check only for string validation
      if fs.Kind() == reflect.String {
          // check value of both fields
         if value==fs.String() {
            return false
         }
      }
   }
   return true
})

The above example only checks for string values, but you can easily modify it for all data types:

type ExampleStruct struct {
   Name string `json:"name" uri:"name" binding:"notoneof=bob rob job"`
   LastName string `json:"last_name" binding:"unique=Name"`
}

Writing custom Gin bindings

In some cases, the client and server use different formats to interchange data. For example, instead of JSON or XML, TOML might be used as the body for a request.

For cases like this, Gin provides a plug-and-play method for changing the body parser.

Every binding needs to implement this interface. The Name method returns a binding name and the Bind methods parse the request body:

type Binding interface {
   Name() string
   Bind(*http.Request, interface{}) error
}

Here’s an example of binding:

type Toml struct {
}

// return the name of binding
func (t Toml) Name() string {
   return "toml"
}

// parse request
func (t Toml) Bind(request *http.Request, i interface{}) error {
// using go-toml package 
   tD:= toml.NewDecoder(request.Body)
// decoding the interface
   return tD.Decode(i)
}

Usage example:

engine.POST("/Toml", func(context *gin.Context) {
   uri:= URI{}

   if err:=context.MustBindWith(&uri, Toml{});err!=nil{
      context.AbortWithError(http.StatusBadRequest,err)
      return
   }
   context.JSON(200,uri)
})

Implementing BindBody to use ShouldBindBodyWith:

func (t Toml) BindBody(bytes []byte, i interface{}) error {
   return toml.Unmarshal(bytes,i)
}

Usage example:

engine.POST("/Toml", func(context *gin.Context) {
   uri:= URI{}

   if err:=context.ShouldBindBodyWith(&uri, Toml{});err!=nil{
      context.AbortWithError(http.StatusBadRequest,err)
      return
   }
   context.JSON(200,uri)
})

Conclusion

In this tutorial, we covered Gin binding, various built-in validators, and some more advanced use cases. We also covered how to build a custom binding using various interfaces provided by the Gin library. Finally, we build some custom validators using advanced reflection and the validator package. You can use these building blocks to build various HTTP body parsers.

The post Gin binding in Go: A tutorial with examples appeared first on LogRocket Blog.

This article was republished from its original source.
Call Us: 1(800)730-2416

Pixeldust is a 20-year-old web development agency specializing in Drupal and WordPress and working with clients all over the country. With our best in class capabilities, we work with small businesses and fortune 500 companies alike. Give us a call at 1(800)730-2416 and let’s talk about your project.

FREE Drupal SEO Audit

Test your site below to see which issues need to be fixed. We will fix them and optimize your Drupal site 100% for Google and Bing. (Allow 30-60 seconds to gather data.)

Powered by

Gin binding in Go: A tutorial with examples

On-Site Drupal SEO Master Setup

We make sure your site is 100% optimized (and stays that way) for the best SEO results.

With Pixeldust On-site (or On-page) SEO we make changes to your site’s structure and performance to make it easier for search engines to see and understand your site’s content. Search engines use algorithms to rank sites by degrees of relevance. Our on-site optimization ensures your site is configured to provide information in a way that meets Google and Bing standards for optimal indexing.

This service includes:

  • Pathauto install and configuration for SEO-friendly URLs.
  • Meta Tags install and configuration with dynamic tokens for meta titles and descriptions for all content types.
  • Install and fix all issues on the SEO checklist module.
  • Install and configure XML sitemap module and submit sitemaps.
  • Install and configure Google Analytics Module.
  • Install and configure Yoast.
  • Install and configure the Advanced Aggregation module to improve performance by minifying and merging CSS and JS.
  • Install and configure Schema.org Metatag.
  • Configure robots.txt.
  • Google Search Console setup snd configuration.
  • Find & Fix H1 tags.
  • Find and fix duplicate/missing meta descriptions.
  • Find and fix duplicate title tags.
  • Improve title, meta tags, and site descriptions.
  • Optimize images for better search engine optimization. Automate where possible.
  • Find and fix the missing alt and title tag for all images. Automate where possible.
  • The project takes 1 week to complete.