Skip to main content
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:
  1. Global middleware (registered with app.Use)
  2. Group middleware (if the route belongs to a group)
  3. Route-level middleware (extra handlers passed to Get, Post, etc.)
  4. 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

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()
}