Skip to main content
Lightning gives you three complementary tools for error handling: a configurable not-found handler, the Recovery() middleware for panics, and ctx.JSONError() for returning structured error responses from your handlers.

Custom 404 Handler

By default, unmatched routes return 404 Not Found as plain text. Override this by passing a NotFoundHandler in your config to lightning.NewApp().
func notFound(ctx *lightning.Context) {
    ctx.JSON(lightning.StatusNotFound, lightning.Map{
        "code":    404,
        "message": "the requested resource does not exist",
    })
}

func main() {
    app := lightning.NewApp(&lightning.Config{
        NotFoundHandler: notFound,
    })

    app.Get("/ping", func(ctx *lightning.Context) {
        ctx.JSON(lightning.StatusOK, lightning.Map{"message": "pong"})
    })

    app.Run(":8080")
}
The not-found handler runs after the global middleware chain, so any middleware you register with app.Use() also applies to 404 responses.

Recovery Middleware

Recovery() catches any panic in a downstream handler or middleware and writes a 500 response instead of crashing the server. It also prints the panic value and stack trace to stderr.

Default behavior

app := lightning.NewApp()
app.Use(lightning.Logger())
app.Use(lightning.Recovery()) // uses the built-in 500 plain-text handler
The built-in handler responds with 500 Internal Server Error as plain text.

Custom panic handler

Pass your own handler to override the default response:
func panicHandler(ctx *lightning.Context) {
    ctx.JSONError(lightning.StatusInternalServerError, "an unexpected error occurred")
}

func main() {
    app := lightning.NewApp()
    app.Use(lightning.Logger())
    app.Use(lightning.Recovery(panicHandler))

    app.Get("/crash", func(ctx *lightning.Context) {
        panic("something went wrong")
    })

    app.Run(":8080")
}
Recovery() accepts at most one custom handler. The panic value and stack trace are always written to stderr regardless of which handler is active.

Programmatic Error Responses

Use ctx.JSONError(code, message) to return a JSON error from any handler. The HTTP status code and the code field in the body are set to the same value.
app.Get("/admin", func(ctx *lightning.Context) {
    token := ctx.Header("Authorization")
    if token == "" {
        ctx.JSONError(lightning.StatusUnauthorized, "missing authorization header")
        return
    }

    ctx.JSON(lightning.StatusOK, lightning.Map{"message": "welcome"})
})
Response body for the unauthorized case:
{"code": 401, "message": "missing authorization header"}

Complete Example: Custom 404 and 500 Handlers

package main

import (
    "github.com/go-labx/lightning"
)

func notFoundHandler(ctx *lightning.Context) {
    ctx.JSONError(lightning.StatusNotFound, "route not found")
}

func panicHandler(ctx *lightning.Context) {
    ctx.JSONError(lightning.StatusInternalServerError, "internal server error")
}

func main() {
    app := lightning.NewApp(&lightning.Config{
        NotFoundHandler: notFoundHandler,
    })

    app.Use(lightning.Logger())
    app.Use(lightning.Recovery(panicHandler))

    app.Get("/ping", func(ctx *lightning.Context) {
        ctx.JSON(lightning.StatusOK, lightning.Map{"message": "pong"})
    })

    // This route deliberately panics to demonstrate recovery
    app.Get("/panic", func(ctx *lightning.Context) {
        panic("simulated failure")
    })

    app.Run(":8080")
}
Test the handlers:
# 404 — unregistered route
curl http://localhost:8080/unknown
# {"code":404,"message":"route not found"}

# 500 — recovered panic
curl http://localhost:8080/panic
# {"code":500,"message":"internal server error"}