Quick Guide

Gin in a Nutshell

8 min read#Go

TL;DR

A quick-start tutorial for the Gin web framework: installing Go and setting up the environment, hot reload, routing and route groups, HTML templates, and middleware.

Gin

Keywords: Gin tutorial Gin documentation Go web framework Go environment setup

Introduction to Gin

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance — up to 40 times faster. If you need smashing performance, get yourself some Gin.

Gin is an HTTP web framework written in Go (Golang), with a clean API and excellent performance. As of version 1.4.0, the entire project, tests included, is only 14K lines, of which about 9K are test code — meaning the framework’s source code is only about 5K lines.

$ find . -name "*_test.go" | xargs cat | wc -l
8657
$ find . -name "*.go" | xargs cat | wc -l
14115

Gin Features

  • Fast: routing does not use reflection, it is based on a radix tree, and memory usage is low.

  • Middleware: HTTP requests can be processed by a chain of middleware first, such as Logger, Authorization, GZIP, and so on. This is very similar to NodeJs’s Koa framework. The middleware mechanism also greatly improves the framework’s extensibility.

  • Crash handling: the service stays available and won’t go down. Gin can catch panics and recover from them, and it offers a very convenient mechanism for handling errors that occur while processing HTTP requests.

  • JSON: Gin can parse and validate the JSON in a request. This feature is especially useful for Restful API development.

  • Route groups: for example, group the APIs that require authorization and those that don’t, or group APIs by version. Groups can be nested without any performance penalty.

  • Built-in rendering: native support for rendering JSON, XML and HTML.

Installing Go & Gin

If you’re a beginner, I recommend reading A Gentle Introduction to Go first. That article covers Go’s basic types, structs, unit testing, concurrency, dependency management and more. For Go 1.13 and above, follow the installation method recommended there.

  • Install Go (Ubuntu)
$ sudo apt-get install golang-go
$ go version
# go version go1.6.2 linux/amd64

The version that ships with Ubuntu is too old; to install a newer version, use the following commands.

$ sudo add-apt-repository ppa:gophers/archive
$ sudo apt-get update
$ sudo apt-get install golang-1.11-go

By default it is installed to /usr/lib/go-1.11, so you need to manually add /usr/lib/go-1.11/bin to your PATH. Add the following line to .bashrc, then run source ~/.bashrc

export PATH=$PATH:/usr/lib/go-1.11/bin

Reference: Golang Ubuntu - Github

  • Install Go (Mac)
$ brew install go
$ go version
# go version go1.12.5 darwin/amd64
  • Set environment variables

Add the GOPATH variable to ~/.bashrc

export GOPATH=~/go
export PATH=$PATH:$GOPATH/bin

After adding them, run source ~/.bashrc

  • Install some auxiliary tool libraries

Due to network issues, golang.org cannot be accessed directly, but the related libraries have been mirrored to Golang - Github

For example, installing go-outline directly fails with a network error, because golang.org/x/tools is a dependency of go-outline.

$ go get -u -v github.com/ramya-rao-a/go-outline
github.com/ramya-rao-a/go-outline (download)
Fetching https://golang.org/x/tools/go/buildutil?go-get=1
https fetch failed: Get https://golang.org/x/tools/go/buildutil?go-get=1: 
dial tcp 216.239.37.1:443: i/o timeout

So you can install it manually from GitHub first, then install go-outline and goreturns.

git clone https://github.com/golang/tools.git $GOPATH/src/golang.org/x/tools
go get -v github.com/ramya-rao-a/go-outline
go get -v github.com/sqs/goreturns
go get -v github.com/rogpeppe/godef

Go has a wealth of helper tools. If you use VSCode, it will prompt you to install the necessary tools one by one, such as static analysis and autocompletion tools.

  • Install Gin
go get -u -v github.com/gin-gonic/gin

-v: print the names of the packages being built -u: if the packages already exist, force an update of them and their dependencies

Your first Gin program

Create a file named main.go in an empty folder.

// geektutu.com
// main.go
package main

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

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.String(200, "Hello, Geektutu")
	})
	r.Run() // listen and serve on 0.0.0.0:8080
}
  1. First, we use gin.Default() to create an instance — this instance is the WSGI application.
  2. Next, we declare a route with r.Get("/", ...), telling Gin which URLs should trigger the function passed in; that function returns the information we want to show in the user’s browser.
  3. Finally, the r.Run() function runs the application on a local server. The default listening port is 8080; you can pass an argument to change it, for example r.Run(":9999") runs on port 9999.
  • Run
$ go run main.go
[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
  • Visit http://localhost:8080 in your browser

Hello Gin

Routing

Routing methods include GET, POST, PUT, PATCH, DELETE and OPTIONS, plus Any, which matches requests of any of these types.

No parameters

// no parameters
r.GET("/", func(c *gin.Context) {
	c.String(http.StatusOK, "Who are you?")
})
$ curl http://localhost:9999/
Who are you?

For curl options, refer to https://man.linuxde.net/curl

Parsing path parameters

Sometimes we need dynamic routes, such as /user/:name, where different URLs pass in different names. In /user/:name/*role, * means the part is optional.

// matches /user/geektutu
r.GET("/user/:name", func(c *gin.Context) {
	name := c.Param("name")
	c.String(http.StatusOK, "Hello %s", name)
})
$ curl http://localhost:9999/user/geektutu
Hello geektutu

Getting query parameters

// matches users?name=xxx&role=xxx, role is optional
r.GET("/users", func(c *gin.Context) {
	name := c.Query("name")
	role := c.DefaultQuery("role", "teacher")
	c.String(http.StatusOK, "%s is a %s", name, role)
})
$ curl "http://localhost:9999/users?name=Tom&role=student"
Tom is a student

Getting POST parameters

// POST
r.POST("/form", func(c *gin.Context) {
	username := c.PostForm("username")
	password := c.DefaultPostForm("password", "000000") // a default value can be set

	c.JSON(http.StatusOK, gin.H{
		"username": username,
		"password": password,
	})
})
$ curl http://localhost:9999/form  -X POST -d 'username=geektutu&password=1234'
{"password":"1234","username":"geektutu"}

Mixing query and POST parameters

// mixing GET and POST
r.POST("/posts", func(c *gin.Context) {
	id := c.Query("id")
	page := c.DefaultQuery("page", "0")
	username := c.PostForm("username")
	password := c.DefaultPostForm("username", "000000") // a default value can be set

	c.JSON(http.StatusOK, gin.H{
		"id":       id,
		"page":     page,
		"username": username,
		"password": password,
	})
})
$ curl "http://localhost:9999/posts?id=9876&page=7"  -X POST -d 'username=geektutu&password=1234'
{"id":"9876","page":"7","password":"1234","username":"geektutu"}

Map parameters (dictionary parameters)

r.POST("/post", func(c *gin.Context) {
	ids := c.QueryMap("ids")
	names := c.PostFormMap("names")

	c.JSON(http.StatusOK, gin.H{
		"ids":   ids,
		"names": names,
	})
})
$ curl -g "http://localhost:9999/post?ids[Jack]=001&ids[Tom]=002" -X POST -d 'names[a]=Sam&names[b]=David'
{"ids":{"Jack":"001","Tom":"002"},"names":{"a":"Sam","b":"David"}}

Redirect

r.GET("/redirect", func(c *gin.Context) {
    c.Redirect(http.StatusMovedPermanently, "/index")
})

r.GET("/goindex", func(c *gin.Context) {
	c.Request.URL.Path = "/"
	r.HandleContext(c)
})
$ curl -i http://localhost:9999/redirect
HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=utf-8
Location: /
Date: Thu, 08 Aug 2019 17:22:14 GMT
Content-Length: 36

<a href="/">Moved Permanently</a>.

$ curl "http://localhost:9999/goindex"
Who are you?

Grouping routes

If you have a group of routes that all start with the prefix /api/v1, does every route need to include that prefix? The answer is no — route groups solve this problem. Route groups also make access control easier: for example, you can put the routes that require login authentication into the same group to simplify authorization.

// group routes
defaultHandler := func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"path": c.FullPath(),
	})
}
// group: v1
v1 := r.Group("/v1")
{
	v1.GET("/posts", defaultHandler)
	v1.GET("/series", defaultHandler)
}
// group: v2
v2 := r.Group("/v2")
{
	v2.GET("/posts", defaultHandler)
	v2.GET("/series", defaultHandler)
}
$ curl http://localhost:9999/v1/posts
{"path":"/v1/posts"}
$ curl http://localhost:9999/v2/posts
{"path":"/v2/posts"}

Uploading files

Single file

r.POST("/upload1", func(c *gin.Context) {
	file, _ := c.FormFile("file")
	// c.SaveUploadedFile(file, dst)
	c.String(http.StatusOK, "%s uploaded!", file.Filename)
})

Multiple files

r.POST("/upload2", func(c *gin.Context) {
	// Multipart form
	form, _ := c.MultipartForm()
	files := form.File["upload[]"]

	for _, file := range files {
		log.Println(file.Filename)
		// c.SaveUploadedFile(file, dst)
	}
	c.String(http.StatusOK, "%d files uploaded!", len(files))
})

HTML templates

type student struct {
	Name string
	Age  int8
}

r.LoadHTMLGlob("templates/*")

stu1 := &student{Name: "Geektutu", Age: 20}
stu2 := &student{Name: "Jack", Age: 22}
r.GET("/arr", func(c *gin.Context) {
	c.HTML(http.StatusOK, "arr.tmpl", gin.H{
		"title":  "Gin",
		"stuArr": [2]*student{stu1, stu2},
	})
})
<!-- templates/arr.tmpl -->
<html>
<body>
    <p>hello, {{.title}}</p>
    {{range $index, $ele := .stuArr }}
    <p>{{ $index }}: {{ $ele.Name }} is {{ $ele.Age }} years old</p>
    {{ end }}
</body>
</html>
$ curl http://localhost:9999/arr

<html>
<body>
    <p>hello, Gin</p>
    <p>0: Geektutu is 20 years old</p>
    <p>1: Jack is 22 years old</p>
</body>
</html>
  • By default, Gin uses the Go standard library’s template packages text/template and html/template. The syntax is identical to the standard library’s and supports rendering for all kinds of complex scenarios.
  • See the official documentation for text/template and html/template

Middleware

// applies globally
r.Use(gin.Logger())
r.Use(gin.Recovery())

// applies to a single route
r.GET("/benchmark", MyBenchLogger(), benchEndpoint)

// applies to a group
authorized := r.Group("/")
authorized.Use(AuthRequired())
{
	authorized.POST("/login", loginEndpoint)
	authorized.POST("/submit", submitEndpoint)
}

How do you write custom middleware?

func Logger() gin.HandlerFunc {
	return func(c *gin.Context) {
		t := time.Now()
		// set a value on the Context instance
		c.Set("geektutu", "1111")
		// before the request
		c.Next()
		// after the request
		latency := time.Since(t)
		log.Print(latency)
	}
}

Debugging with hot reload

Python’s Flask framework has a debug mode: pass in debug=True at startup and you get hot reload (Hot Reload, Live Reload). Change the source code, save, and the update is triggered automatically — just refresh your browser. No more killing processes and restarting.

Gin doesn’t support this natively, but there are many additional libraries that do. For example:

  • github.com/codegangsta/gin
  • github.com/pilu/fresh

Here we’ll use github.com/pilu/fresh.

go get -v -u github.com/pilu/fresh

Once it’s installed, just replace the go run main.go command with fresh. Every time you change a source file, the code is recompiled automatically (Auto Compile).

Reference: github.com/pilu/fresh - Github

The Chinese original of this article is available at geektutu.com/post/quick-go-gin.html.

Found this helpful? Buy me a coffee ☕

Alipay
WeChat

Comments