A route group lets you collect related routes under a common URL prefix and attach middleware that runs only for those routes. You create a group with app.Group(prefix) and register routes on the returned *Group value exactly as you would on the application.
Creating a group
app := lightning.NewApp()
api := app.Group("/api")
api.Get("/users", listUsers) // → GET /api/users
api.Post("/users", createUser) // → POST /api/users
api.Get("/users/:id", getUser) // → GET /api/users/:id
The *Group type exposes the same HTTP method shortcuts as *Application: Get, Post, Put, Patch, Delete, Head, Options, and AddRoute.
Adding middleware to a group
Call group.Use() to register middleware that runs only for routes in that group. Global middleware registered on the application still runs first.
app := lightning.NewApp()
app.Use(lightning.Logger()) // runs for every request
api := app.Group("/api")
api.Use(func(ctx *lightning.Context) {
token := ctx.Header("Authorization")
if token == "" {
ctx.JSONError(lightning.StatusUnauthorized, "missing token")
return // do not call ctx.Next() — short-circuit the chain
}
ctx.Next()
})
api.Get("/profile", func(ctx *lightning.Context) {
ctx.JSON(lightning.StatusOK, lightning.Map{"user": "alice"})
})
The execution order for GET /api/profile:
Logger (global)
auth check (group)
handler
A request to a route outside the group (e.g. GET /ping) skips the group middleware entirely.
Nested groups
Call group.Group(prefix) to create a child group that inherits the parent’s prefix and middleware.
api := app.Group("/api")
v1 := api.Group("/v1")
v1.Get("/users", listUsersV1) // → GET /api/v1/users
v2 := api.Group("/v2")
v2.Get("/users", listUsersV2) // → GET /api/v2/users
Middleware is additive: a route in a nested group runs the global middleware, then the parent group’s middleware, then the child group’s middleware, and finally the route handler.
Complete example
The example below builds an /api/v1 group with authentication applied to all routes, and a nested /api/v1/admin group with an additional admin-only check.
package main
import "github.com/go-labx/lightning"
func requireAuth(ctx *lightning.Context) {
if ctx.Header("Authorization") == "" {
ctx.JSONError(lightning.StatusUnauthorized, "unauthorized")
return
}
ctx.SetData("userRole", "user")
ctx.Next()
}
func requireAdmin(ctx *lightning.Context) {
role, _ := ctx.GetData("userRole").(string)
if role != "admin" {
ctx.JSONError(lightning.StatusForbidden, "forbidden")
return
}
ctx.Next()
}
func main() {
app := lightning.DefaultApp()
// Public route — no auth required
app.Get("/ping", func(ctx *lightning.Context) {
ctx.Text(lightning.StatusOK, "pong")
})
// /api/v1 group — all routes require a valid token
v1 := app.Group("/api").Group("/v1")
v1.Use(requireAuth)
v1.Get("/users", func(ctx *lightning.Context) {
ctx.JSON(lightning.StatusOK, lightning.Map{
"users": []string{"alice", "bob"},
})
})
v1.Post("/users", func(ctx *lightning.Context) {
ctx.JSON(lightning.StatusOK, lightning.Map{"status": "created"})
})
v1.Get("/users/:id", func(ctx *lightning.Context) {
id := ctx.Param("id")
ctx.JSON(lightning.StatusOK, lightning.Map{"id": id})
})
// /api/v1/admin nested group — additionally requires admin role
admin := v1.Group("/admin")
admin.Use(requireAdmin)
admin.Get("/stats", func(ctx *lightning.Context) {
ctx.JSON(lightning.StatusOK, lightning.Map{"requests": 1024})
})
app.Run()
}
Request flow for GET /api/v1/users:
Logger (global, via DefaultApp)
Recovery (global, via DefaultApp)
requireAuth (v1 group)
handler
Request flow for GET /api/v1/admin/stats:
Logger (global)
Recovery (global)
requireAuth (v1 group)
requireAdmin (admin nested group)
handler
Group middleware is cached after the first route registration on a group. Call group.Use() before registering any routes to ensure all middleware is included.
Using AddRoute on a group
When you need to register a route for a method determined at runtime, group.AddRoute works the same as app.AddRoute but prepends the group prefix and group middleware.
group.AddRoute("GET", "/status", []lightning.HandlerFunc{
func(ctx *lightning.Context) {
ctx.Text(lightning.StatusOK, "ok")
},
})