go 45 lines · 6 steps

Header-driven API versioning in Gin

A Gin handler binds request headers to a struct and shapes its JSON response based on the client's requested API version.

Explained by highlit
1package handlers
2 
3import (
4 "net/http"
5 
6 "github.com/gin-gonic/gin"
7)
8 
9type clientMeta struct {
10 APIVersion string `header:"X-Api-Version" binding:"required"`
11 Platform string `header:"X-Client-Platform"`
12 RequestID string `header:"X-Request-Id"`
13}
14 
15func GetProfile(svc ProfileService) gin.HandlerFunc {
16 return func(c *gin.Context) {
17 var meta clientMeta
18 if err := c.ShouldBindHeader(&meta); err != nil {
19 c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
20 "error": "missing or invalid X-Api-Version header",
21 })
22 return
23 }
24 
25 userID := c.Param("id")
26 profile, err := svc.Fetch(c.Request.Context(), userID)
27 if err != nil {
28 c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "profile not found"})
29 return
30 }
31 
32 switch meta.APIVersion {
33 case "2024-10", "2024-06":
34 c.JSON(http.StatusOK, newProfileV2(profile))
35 case "2023-01":
36 c.JSON(http.StatusOK, newProfileV1(profile))
37 default:
38 c.JSON(http.StatusNotAcceptable, gin.H{
39 "error": "unsupported API version",
40 "requested": meta.APIVersion,
41 "supported": []string{"2024-10", "2024-06", "2023-01"},
42 })
43 }
44 }
45}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Binding headers into a tagged struct lets you validate required metadata in one call instead of reading each header by hand.
  2. 2Returning a closure that captures dependencies keeps handlers testable while fitting Gin's HandlerFunc signature.
  3. 3Switching on a version header lets one endpoint serve multiple response shapes without breaking older clients.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Header-driven API versioning in Gin — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code