Web APIs with the Gin Framework
Gin is a fast, popular HTTP web framework for Go built on top of net/http . You'll create a router with gin.Default() , define routes, read path and query parameters, return JSON, bind and validate request bodies into structs, and compose middleware and route groups — far less boilerplate than the standard library alone.
Learn Web APIs with the Gin Framework in our free Go course — an interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ Hello, Gin: gin.Default() and a route
gin.Default() gives you a router (an *gin.Engine ) with logging and panic-recovery already attached. Register a route with the verb method ( r.GET ), and reply with c.JSON . gin.H is just a handy map[string]any for JSON.
2️⃣ Reading input: path and query params
Two common input sources: a path parameter declared with :id and read with c.Param("id") , and a query parameter from the URL read with c.Query("q") (or c.DefaultQuery for a fallback).
3️⃣ Binding & validating JSON
For POST / PUT bodies, define a struct with json and binding tags, then call c.ShouldBindJSON(&in) . It unmarshals and validates, returning an error you handle (usually a 400 ).
4️⃣ Middleware & route groups
Middleware wraps your handlers to run shared logic. Register it with r.Use(...) ; call c.Next() to continue the chain, and code after it runs on the way back out. A route group bundles routes under a shared prefix.
🎯 Your Turn
Build a greeting endpoint. Fill in the blanks marked ___ to read the path param and reply with JSON.
❌ Using c.Query for a path segment — returns empty for /users/:id .
✅ Read declared :id segments with c.Param("id") .
❌ Not returning after a binding error — the handler keeps going and writes twice.
❌ Forgetting the binding:"required" tag — missing fields silently become zero values.
✅ Tag required fields so ShouldBindJSON rejects incomplete bodies.
❌ Omitting c.Next() in middleware — the chain stalls and the handler never runs.
✅ Call c.Next() to continue (or c.Abort() to stop deliberately).
c.Param("id") . Query-string values like ?q=go use c.Query("q") instead.
c.ShouldBindJSON(&obj) . It returns an error without auto-aborting, so you reply with your own 400.
Write a POST /echo route: define a Message struct with a required text field, bind it, and echo it back — or return a 400 on bad input.
Practice quiz
What does gin.Default() return?
- A *gin.Engine with the Logger and Recovery middleware already attached
- A database handle
- A bare router with no middleware
- A plain http.Server
Answer: A *gin.Engine with the Logger and Recovery middleware already attached. gin.Default() builds an Engine that already includes the Logger and Recovery middleware; gin.New() gives you a bare engine.
How do you register a handler for GET /ping?
- r.Route("GET", "/ping")
- r.Add("/ping")
- r.GET("/ping", handler)
- r.Handle("/ping")
Answer: r.GET("/ping", handler). Gin has a method per HTTP verb; r.GET(path, handler) registers a GET route.
Inside a handler, how do you read a path parameter from /users/:id?
- c.Query("id")
- c.Param("id")
- c.Path("id")
- c.Get("id")
Answer: c.Param("id"). c.Param reads named segments declared with a colon in the route, like :id.
Which call reads a URL query string value like ?q=go?
- c.Header("q")
- c.Param("q")
- c.Form("q")
- c.Query("q")
Answer: c.Query("q"). c.Query returns the value of a URL query parameter; c.DefaultQuery lets you supply a fallback.
How do you send a JSON response with status 200?
- c.JSON(http.StatusOK, obj)
- c.Send(obj)
- c.Render(obj)
- c.Write(200, obj)
Answer: c.JSON(http.StatusOK, obj). c.JSON(statusCode, value) serializes the value to JSON and sets the Content-Type header.
Which method binds the request body into a struct AND returns an error you can handle?
- c.BindJSON only
- c.ShouldBindJSON(&obj)
- c.ParseJSON(&obj)
- c.ReadJSON(&obj)
Answer: c.ShouldBindJSON(&obj). c.ShouldBindJSON unmarshals and validates the body, returning an error without aborting the request automatically.
How do you require a JSON field during binding?
- A json:"!" tag
- It is required by default
- Call c.Require()
- A struct tag of binding:"required"
Answer: A struct tag of binding:"required". Gin reads validator tags like binding:"required" on struct fields to enforce presence and other rules.
What is the right way to add middleware to every route on an engine?
- r.Before(mw)
- r.Apply(mw)
- r.Use(mw)
- r.Middleware(mw)
Answer: r.Use(mw). r.Use(mw) registers middleware that runs for all routes registered afterward.
Inside middleware, what advances to the next handler in the chain?
- return
- c.Next()
- c.Skip()
- c.Continue()
Answer: c.Next(). c.Next() runs the remaining handlers; code after it runs on the way back out, useful for timing and logging.
Compared with the standard net/http, Gin mainly adds...
- routing with params, JSON helpers, binding/validation, and middleware groups
- a different network stack
- support for only GET requests
- a built-in database
Answer: routing with params, JSON helpers, binding/validation, and middleware groups. Gin builds on net/http but layers on a fast router, JSON helpers, request binding/validation, and a middleware/group system.