Gin Introduction
What Gin adds over plain net/http, installing it, and a minimal hello-world server.
What Gin adds over net/http
Go's standard library (net/http) is genuinely production-capable on its own — see the Go track's REST API with net/http page for a from-scratch example. Gin is a web framework built on top of it that removes the boilerplate that starts to hurt once an API grows past a handful of endpoints:
- A real router with path parameters and grouping —
r.GET("/users/:id", ...), plus route groups (r.Group("/api/v1")) for shared prefixes and middleware, instead of hand-parsing paths or juggling multipleServeMuxinstances. - A built-in middleware chain —
r.Use(...), withc.Next()/c.Abort()control, and per-route middleware, covered in depth on the next page. - JSON binding and validation in one call —
c.ShouldBindJSON(&input)decodes and validates struct tags (binding:"required") in a single line, instead of a manualjson.Decodeplus hand-written checks. - Performance — Gin's router (a radix-tree implementation) is one of the fastest in the Go ecosystem, adding negligible overhead over raw
net/http.
Gin doesn't replace your knowledge of net/http — a gin.Context wraps the same underlying http.Request/http.ResponseWriter you'd use directly, and *gin.Engine itself implements http.Handler, so it plugs into anything expecting the standard interface (including net/http's own http.Server).
Installing Gin
Gin is a regular Go module — no separate installer:
go mod init example.com/ginapp
go get github.com/gin-gonic/gin
A minimal Gin server
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default() // includes sane default middleware: logging + panic recovery
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run(":8080") // shorthand for http.ListenAndServe(":8080", r)
}
go run main.go
curl http://localhost:8080/ping
# {"message":"pong"}
gin.Default() vs gin.New(): Default() pre-attaches Gin's Logger() and Recovery() middleware (so a panicking handler returns a 500 instead of crashing the whole process); New() gives you a completely bare engine to configure from scratch.
Path parameters and gin.H
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id") // path parameter
name := c.DefaultQuery("name", "anonymous") // query parameter with a fallback
c.JSON(http.StatusOK, gin.H{
"id": id,
"name": name,
})
})
curl "http://localhost:8080/users/42?name=Zara"
# {"id":"42","name":"Zara"}
gin.H is simply a shorthand type alias for map[string]any — used purely for readability when building a JSON response inline.
Common mistakes
- Using
gin.New()in production without addinggin.Recovery()back manually — an unhandled panic in a handler then crashes the entire server instead of returning a 500 to that one caller. - Forgetting that
c.Param("id")always returns astring— you still need to parse it (strconv.Atoi) and check the error before treating it as a number. - Running with Gin's default debug mode in production — set
gin.SetMode(gin.ReleaseMode)(or theGIN_MODE=releaseenvironment variable) to disable the verbose debug output and warnings.
Interview questions
Q: What does gin.Default() give you that gin.New() doesn't?
gin.Default() returns an engine with the Logger() and Recovery() middleware already attached — request logging and automatic panic recovery (turning a panic into a 500 response instead of crashing the process). gin.New() returns a bare engine with neither, for full manual control.
Q: Is a Gin application still compatible with net/http?
Yes — *gin.Engine implements the standard http.Handler interface, and gin.Context internally wraps the same *http.Request and http.ResponseWriter you'd use with plain net/http. This means Gin apps can be served by the standard http.Server, and you can reach into c.Request/c.Writer directly whenever you need standard-library-level control.