Learn Chi - Subrouters, Groups & Mounting
Series/Learn Chi/Episode 5
Episode 5 of 23

Learn Chi - Subrouters, Groups & Mounting

This episode teaches how to organize routes at scale: subrouters with r.Route, groups sharing middleware with r.Group, and mounting sub-apps with r.Mount. You will also learn to separate router resources and apply middleware per subrouter selectively.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Real applications are never satisfied with a single router file. Episode 5 teaches the structure that keeps routes tidy as your application grows: subrouters, groups, and mounting. These three mechanisms are how chi manages complexity without introducing new handler types.

The patterns you'll master here become the skeleton of the whole project structure in episode 8. If you've ever been confused by middleware not running on some routes, the answer is almost always in this episode.

Subrouters with r.Route

Separating a Prefix

r.Route creates a child router that inherits the parent router's middleware and lives under a single prefix:

/api subrouter
func main() {
    r := chi.NewRouter()
    r.Use(middleware.Logger)
 
    r.Route("/api", func(api chi.Router) {
        api.Get("/ping", func(w http.ResponseWriter, req *http.Request) {
            w.Write([]byte("pong"))
        })
        api.Get("/health", healthHandler)
    })
 
    http.ListenAndServe(":8080", r)
}

All routes inside r.Route("/api", ...) automatically live under /api. api.Get("/ping", handler) produces the endpoint /api/ping. The child router inherits middleware.Logger from the parent without having to register it again.

Nested Subrouters

Resources can be broken down even further with nested routes:

Nested subrouters
r.Route("/users", func(users chi.Router) {
    users.Get("/", listUsers)
    users.Post("/", createUser)
    users.Route("/{id}", func(user chi.Router) {
        user.Get("/", getUser)
        user.Put("/", updateUser)
        user.Delete("/", deleteUser)
    })
})

This structure mirrors the URL hierarchy: /users, /users/42, /users/42 with different methods. user.Get("/", getUser) inside the /{id} subrouter handles /users/42.

Groups with r.Group

Sharing Middleware Without a Prefix

r.Group creates a new scope that uses certain middleware without changing the path:

Group with shared middleware
r.Group(func(admin chi.Router) {
    admin.Use(middleware.BasicAuth("app",
        map[string]string{"admin": "rahasia"}))
    admin.Get("/admin/stats", statsHandler)
})
 
r.Get("/public", publicHandler)

The admin group uses Basic Auth, while the /public route outside the group is unaffected. r.Group(func(admin chi.Router){:go}... is the way to separate areas that need different protection without a separate prefix.

When to Use a Group

Use r.Group when:

  • Different areas need different middleware on the same path.
  • You want to apply middleware to only a few routes.
  • You need a temporary sub-scope without moving entire routes.

Use r.Route when the paths genuinely differ and you want to clean them up from repeated prefixes.

Mounting Sub-apps

Attaching an Independent Router

r.Mount attaches another router — even someone else's — as a handler under a prefix:

Mount sub-app v1
func main() {
    r := chi.NewRouter()
 
    v1 := chi.NewRouter()
    v1.Use(middleware.Recoverer)
    v1.Get("/status", statusHandler)
 
    r.Mount("/v1", v1)
    http.ListenAndServe(":8080", r)
}

r.Mount("/v1", v1) makes all routes in v1 reachable via /v1/status. The router v1 stands on its own with its own Recoverer middleware — it is not a child of r.

Mounting Handlers That Aren't Routers

Because Mount accepts an http.Handler, you can attach anything:

Mount another handler
fileServer := http.FileServer(http.Dir("./static"))
r.Mount("/assets", http.StripPrefix("/assets", fileServer))

http.StripPrefix("/assets", fileServer) removes the prefix before forwarding to the file server — a pattern we'll reuse in episode 7 for static files.

Organizing Router Resources

Splitting into Functions

For real projects, move each resource into its own router function:

Separate resource routers
func usersRouter() http.Handler {
    users := chi.NewRouter()
    users.Get("/", listUsers)
    users.Post("/", createUser)
    users.Route("/{id}", func(user chi.Router) {
        user.Get("/", getUser)
        user.Put("/", updateUser)
    })
    return users
}
 
func main() {
    r := chi.NewRouter()
    r.Mount("/users", usersRouter())
    r.Mount("/products", productsRouter())
    http.ListenAndServe(":8080", r)
}

usersRouter() and productsRouter() each return an http.Handler, then get mounted at the root. Every resource now stands alone and is easy to test.

Reusing Middleware per Subrouter

Selective Middleware

The combination of With and subrouters gives you precise middleware control:

Selective middleware
r.Route("/api", func(api chi.Router) {
    api.Use(middleware.Throttle(100))
 
    api.Route("/admin", func(admin chi.Router) {
        admin.Use(middleware.BasicAuth("app",
            map[string]string{"root": "s3cret"}))
        admin.Get("/dashboard", dashboardHandler)
    })
 
    api.With(middleware.Compress(5)).Get("/export", exportHandler)
})

api.With(middleware.Compress(5)).Get("/export", ...) applies inline middleware to only one route — a pattern called inline middleware, strengthened further in version 5.3 as we'll discuss in episode 20.

Conclusion

Key takeaways:

  • r.Route creates a subrouter with a prefix and inherits the parent's middleware.
  • r.Group scopes middleware without changing the path.
  • r.Mount attaches another router or handler as a sub-app.
  • Resource routers can be split into functions that return an http.Handler.
  • With provides inline middleware per route.
  • The combination of all three keeps a large application structure maintainable.

In the next episode 6 we focus on chi's backbone: middleware and chains — writing your own middleware, using r.Use, composing with middleware.Chain, and exploring built-in middleware like RequestID, Logger, Recoverer, and BasicAuth.

Learn Chi - Subrouters, Groups & Mounting | Learn Chi