Middleware in Lightning is any function with the HandlerFunc signature:
type HandlerFunc func(*Context)
The Middleware type is an alias for HandlerFunc, so the two are interchangeable. Middleware and route handlers share the same type — a middleware is simply a handler that calls ctx.Next() to pass control to the next function in the chain.
How the chain works
When a request arrives, Lightning executes the handlers in order:
- Global middleware (registered with
app.Use)
- Group middleware (if the route belongs to a group)
- Route-level middleware (extra handlers passed to
Get, Post, etc.)
- The final route handler
Each function in the chain calls ctx.Next() to advance to the next one. You can run logic before ctx.Next() (pre-processing) and logic after it (post-processing).
app.Use(func(ctx *lightning.Context) {
// runs before the handler
ctx.Next()
// runs after the handler
})
Global middleware
Register middleware that runs for every request with app.Use. You can pass multiple middlewares in a single call or chain multiple Use calls.
app := lightning.NewApp()
app.Use(func(ctx *lightning.Context) {
fmt.Println("middleware 1 --->")
ctx.Next()
fmt.Println("<--- middleware 1")
})
app.Use(func(ctx *lightning.Context) {
fmt.Println("middleware 2 --->")
ctx.Next()
fmt.Println("<--- middleware 2")
})
app.Get("/", func(ctx *lightning.Context) {
ctx.JSON(lightning.StatusOK, lightning.Map{"message": "hello world"})
})
For a request to /, the execution order is:
middleware 1 --->
middleware 2 --->
handler
<--- middleware 2
<--- middleware 1
Route-level middleware
Pass extra handler functions before the final handler to Get, Post, or any other method registration. These run only for that specific route.
authCheck := 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()
}
app.Get("/dashboard", authCheck, func(ctx *lightning.Context) {
ctx.JSON(lightning.StatusOK, lightning.Map{"page": "dashboard"})
})
Writing a custom middleware
Here is a complete auth middleware that validates a bearer token before allowing the request to proceed.
func AuthMiddleware(ctx *lightning.Context) {
token := ctx.Header("Authorization")
if token != "Bearer secret-token" {
ctx.JSONError(lightning.StatusUnauthorized, "unauthorized")
return // stop the chain; do not call ctx.Next()
}
ctx.Next()
}
Attach it globally or per-route:
// Global — applies to every route
app.Use(AuthMiddleware)
// Route-level — applies only to this route
app.Get("/profile", AuthMiddleware, func(ctx *lightning.Context) {
ctx.JSON(lightning.StatusOK, lightning.Map{"user": "alice"})
})
If you want to abort the request (e.g. due to a failed auth check), return from the middleware without calling ctx.Next(). Lightning will not advance the chain.
Built-in middleware
Lightning ships two middleware functions you can use immediately.
Logger
Logger() logs each request after it completes, reporting the remote address, method, status code, path, elapsed time, and user agent.
app.Use(lightning.Logger())
Sample log output:
127.0.0.1:54321 GET 200 /users 3ms Mozilla/5.0 ...
Logger calls ctx.Next() internally and logs on the way back, so it measures total handler time correctly.
Recovery
Recovery() catches any panic that occurs downstream, writes a stack trace to stderr, and returns a 500 response. This prevents a single panicking handler from crashing the entire server.
app.Use(lightning.Recovery())
You can supply a custom handler to control the 500 response:
app.Use(lightning.Recovery(func(ctx *lightning.Context) {
ctx.Fail(9999, "something went wrong, please try again")
}))
Using both together
lightning.DefaultApp() is a convenience constructor that registers Logger and Recovery for you:
app := lightning.DefaultApp()
// equivalent to:
// app := lightning.NewApp()
// app.Use(lightning.Logger())
// app.Use(lightning.Recovery())
Complete example
Global middleware
Route-level middleware
package main
import (
"fmt"
"github.com/go-labx/lightning"
)
func main() {
app := lightning.NewApp()
app.Use(lightning.Logger())
app.Use(lightning.Recovery())
app.Use(func(ctx *lightning.Context) {
fmt.Println("request started")
ctx.Next()
fmt.Println("request finished, status:", ctx.Status())
})
app.Get("/ping", func(ctx *lightning.Context) {
ctx.Text(lightning.StatusOK, "pong")
})
app.Run()
}
package main
import "github.com/go-labx/lightning"
func requireAuth(ctx *lightning.Context) {
if ctx.Header("Authorization") == "" {
ctx.JSONError(lightning.StatusUnauthorized, "unauthorized")
return
}
ctx.Next()
}
func main() {
app := lightning.DefaultApp()
// Public route — no auth
app.Get("/ping", func(ctx *lightning.Context) {
ctx.Text(lightning.StatusOK, "pong")
})
// Protected route — requireAuth runs first
app.Get("/me", requireAuth, func(ctx *lightning.Context) {
ctx.JSON(lightning.StatusOK, lightning.Map{"user": "alice"})
})
app.Run()
}