This episode dissects Gin's internal architecture: gin.Engine as the core of the framework, the radix tree router for matching paths and methods, the middleware chain that runs sequentially, and gin.Context, which carries the request and response.

Before writing more code, it's important to understand what happens behind the scenes every time Gin receives a request. This episode dissects Gin's main architecture: how gin.Engine is the center of everything, how the router matches URLs, how middleware chains together, and how gin.Context is the bridge between request and response.
This understanding isn't just theory. When you face bugs like routing conflicts, middleware that doesn't get called, or panics in handlers, this architectural knowledge is what guides you to the root cause.
gin.Engine is the representation of the framework instance: it stores the router, the list of global middleware, the mode configuration (debug/release), and other HTTP configuration. Every Gin application has at least one Engine, created via gin.New() or gin.Default().
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.New()
r.Use(gin.Logger())
r.Run(":8080")
}The differences between gin.New(), gin.Default(), and the debug/release modes will be discussed in detail in episode 3. For now, just understand that the Engine is the central object managing all routes and middleware.
Every time you call r.GET("/users/:id", handler), Gin does not store that route in a plain slice. The route is inserted into a dedicated radix tree (compressed trie) that groups common prefixes. When a request arrives, Gin traverses this tree based on path and method, so lookups complete in a single pass.
r.GET("/users", listUsers)
r.GET("/users/:id", getUser)
r.POST("/users", createUser)For the routes above, the radix tree merges the /users prefix into a single node, with a /:id branch and different method handlers. This is why routes sharing a prefix don't slow down lookups.
/
└── users
├── (GET) -> listUsers
├── (POST) -> createUser
└── :id
└── (GET) -> getUserMiddleware is a function func(c *gin.Context) that runs in sequence. Gin holds a list of handlers for a route — global middleware plus the final handler — and executes them like a chain: each middleware can call c.Next() to proceed to the next handler, or c.Abort() to stop.
r := gin.New()
r.Use(gin.Logger()) // ke-1
r.Use(gin.Recovery()) // ke-2
r.GET("/ping", ping) // ke-3 (handler final)The execution flow: Logger starts → Recovery starts → ping executes → Recovery finishes → Logger finishes (usually writing the log after the handler completes). This ordering is crucial and will be explored in depth in episode 6.
Gin has several key types you'll encounter throughout this series:
/api/v1 group.*gin.Context): carries all request, response, and per-request state information.func(c *gin.Context).api := r.Group("/api/v1")
api.Use(requireAuth)
api.GET("/users", func(c *gin.Context) {
c.JSON(200, gin.H{"data": "daftar user"})
})
r.Run(":8080")The function r.Group("/api/v1") creates a RouterGroup with a shared prefix. Every route inside the group automatically inherits the middleware installed on that group.
gin.Context is the object you'll hold most often. Each request produces a new Context that carries:
c.Request (type *http.Request).c.Writer.c.Param("id").c.Query("page").c.Set(key, value) and c.Get(key).func detailHandler(c *gin.Context) {
id := c.Param("id")
page := c.DefaultQuery("page", "1")
c.Set("userID", id)
c.JSON(200, gin.H{"id": id, "page": page})
}The method c.DefaultQuery("page", "1") returns the query parameter with a default value if it isn't provided. Meanwhile, c.Set and c.Get are the way to share data between middleware, such as storing a userID after authentication.
Gin runs in debug mode by default, which displays route logs and is more verbose. For production, set release mode:
GIN_MODE=release go run main.gogin.SetMode(gin.ReleaseMode)
r := gin.Default()The function gin.SetMode(gin.ReleaseMode) disables route logs at startup and reduces debug overhead. Episode 19 will explore this optimization further.
Key takeaways:
gin.Engine is the center of the framework: it stores the router, middleware, and configuration.c.Next() and can be stopped via c.Abort().gin.Context carries the request, response, params, and per-request state.In the next episode, episode 3, we'll start writing real code: setup & hello world — project initialization, installing Gin, comparing gin.New() with gin.Default(), creating your first routes with GET/POST/PUT/DELETE, and running the server with engine.Run.