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.