How to Use Route Groups and Nested Groups in Gin

Route groups in Gin let you organize URLs under common prefixes and attach middleware to specific logical sections, while nested groups automatically concatenate paths via calculateAbsolutePath and inherit parent middleware chains for hierarchical API structures.

Organizing HTTP endpoints in a web framework requires clean URL prefixes and reusable middleware stacks. In the gin-gonic/gin repository, the RouterGroup type provides the foundation for both flat and hierarchical routing structures. Understanding how to use route groups and nested groups in Gin allows you to build modular APIs where authentication, logging, and versioning are applied precisely where needed.

Understanding RouterGroup Core Concepts

The RouterGroup struct defined in routergroup.go manages collections of routes that share a common base path and handler chain. When you invoke the Group method, Gin instantiates a new group whose base path is the parent’s path joined with the supplied relativePath (source lines 70-76 in routergroup.go). This concatenation logic resides in calculateAbsolutePath, which ensures clean joining— for example, merging /v1 with /admin produces /v1/admin without double slashes (source lines 50-52 in routergroup.go).

Each group maintains its own slice of middleware handlers. Calling Use on a group appends middleware to that slice, meaning those handlers execute only for routes registered on that group and its descendants (source lines 64-68 in routergroup.go). When you register a route using methods like GET or POST, the internal handle function resolves the absolute URL by combining the group’s base path with the route’s relative path before registering it with the underlying HTTP router (source lines 86-90 in routergroup.go).

Creating Basic Route Groups

Start by calling Group on an existing RouterGroup—including the root Engine created by gin.Default() or gin.New(). This returns a new group that inherits the parent’s middleware but can accept its own via the Use method.

router := gin.Default()

// Create a group with prefix /api
api := router.Group("/api")
api.Use(AuthMiddleware)

// These resolve to /api/users and /api/users
api.GET("/users", listUsers)
api.POST("/users", createUser)

In this example, AuthMiddleware executes only for routes defined on the api group. The root engine and other groups remain unaffected.

Implementing Nested Route Groups

Nested groups enable versioning and administrative sub-sections without repeating path prefixes. When you call Group on an existing group rather than the engine, Gin invokes calculateAbsolutePath to build the child’s base path by appending the new segment to the parent’s (source lines 50-52 in routergroup.go). The child also inherits the parent’s middleware chain, allowing you to stack additional handlers for deeper levels.

router := gin.Default()

v1 := router.Group("/v1")
v1.Use(Logger())

// Nested group: base path becomes /v1/admin
admin := v1.Group("/admin")
admin.Use(AdminOnly())

admin.GET("/stats", adminStats)       // Resolves to GET /v1/admin/stats
admin.POST("/users", adminCreateUser) // Resolves to POST /v1/admin/users

Requests to /v1/admin/stats execute Logger() (from the parent), then AdminOnly() (from the child), then adminStats. This composable architecture keeps your security layers DRY while isolating administrative logic.

Advanced Route Registration Methods

Groups support flexible HTTP method registration beyond standard verbs. Use Any to respond to all HTTP methods—this loops over Gin’s internal list of all methods and registers the handler for each (source lines 45-48 in routergroup.go). Use Match to restrict specific verbs (source lines 55-60 in routergroup.go).

api := router.Group("/api")

// Responds to GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
api.Any("/ping", pingHandler)

// Only responds to GET or POST at /api/upload
api.Match([]string{http.MethodGet, http.MethodPost}, "/upload", uploadHandler)

Inspecting Group Paths with BasePath

For debugging or dynamic route generation, call BasePath() to retrieve the fully resolved prefix of any group. This method returns the basePath field computed during group creation (source lines 80-84 in routergroup.go).

v1 := router.Group("/v1")
admin := v1.Group("/admin")
fmt.Println(admin.BasePath()) // Output: /v1/admin

This is particularly useful when splitting route definitions across multiple files and needing to verify the final URL structure at runtime.

Summary

  • RouterGroup is the core type in routergroup.go for organizing routes with common prefixes and middleware stacks in Gin.
  • The Group method creates new groups; nested groups automatically concatenate paths via calculateAbsolutePath (lines 50-52) to build hierarchical URLs like /v1/admin.
  • Middleware added via Use applies to the group and all its children, enabling composable security and logging layers that execute in parent-to-child order.
  • Flexible registration methods Any and Match allow a single group to handle multiple HTTP verbs with precise control.
  • The BasePath() method (lines 80-84) exposes the resolved URL prefix for runtime introspection.

Frequently Asked Questions

How do route groups handle middleware inheritance in Gin?

When you create a nested group, the child inherits the parent’s handler chain because Gin copies the parent’s middleware slice into the child during instantiation. When Use is called on the child, it appends new handlers to this inherited slice (source lines 64-68 in routergroup.go). Consequently, parent middleware executes first, followed by child-specific middleware, creating a natural cascading execution order for authentication, logging, and authorization.

What is the difference between gin.Default() and gin.New() when working with groups?

Both functions return an Engine instance that embeds a RouterGroup, but gin.Default() automatically attaches the Logger and Recovery middleware to the root group defined in gin.go. When you call Group on either engine, the resulting child group behavior is identical; the only difference lies in the root middleware stack present before you define your own groups.

Can I register routes directly on the root engine without creating a group?

Yes. Because Engine embeds RouterGroup, methods like GET, POST, and Group are available directly on the engine instance. This is functionally equivalent to creating a group with an empty base path, as the root engine initializes with basePath set to an empty string (source lines 70-76 in routergroup.go). Routes registered directly on the engine have no URL prefix unless you specify one in the path argument.

How does Gin prevent duplicate slashes when nesting groups with trailing slashes?

The calculateAbsolutePath function in routergroup.go normalizes path concatenation by cleaning the resulting string, ensuring that joining /v1/ with /admin produces /v1/admin rather than /v1//admin (source lines 50-52). To ensure consistent behavior across all Gin versions, always include a leading slash in your relative path arguments when calling Group.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →