Middleware
Middleware in λ Cosmos wraps handlers to add cross-cutting behavior.
type Handler func(w http.ResponseWriter, r *http.Request) error
type Middleware = func(Handler) HandlerApply middleware with Use:
app := framework.New()app.Use(middleware.Recover())app.Use(middleware.Logger(logger))Middleware runs in registration order — the first Use call wraps outermost.
Recover
Section titled “Recover”Catches panics and converts them into errors that flow through the normal error handling chain:
import "github.com/studiolambda/cosmos/framework/middleware"
app.Use(middleware.Recover())The default recovery handler converts panic values to errors based on their type:
| Panic type | Conversion |
|---|---|
error | Returned as-is |
string | Wrapped in errors.New() |
fmt.Stringer | Uses String() method |
io.Reader | Reads all bytes |
encoding.TextMarshaler | Uses MarshalText() |
| Other | Formatted with fmt.Sprintf and joined with ErrRecoverUnexpected |
For custom panic handling, use RecoverWith:
app.Use(middleware.RecoverWith(func(value any) error { // custom panic-to-error conversion sentry.CaptureException(fmt.Errorf("panic: %v", value)) return fmt.Errorf("internal error")}))Logger
Section titled “Logger”Logs failed requests using structured logging (log/slog):
import "log/slog"
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))app.Use(middleware.Logger(logger))The logger fires after the response is complete and logs when:
- The handler returned an error, or
- The response status code is 5xx.
Logged fields: method, url, status, err.
Passing nil creates a discard logger (no panics, no output).
Protects against Cross-Site Request Forgery using Go’s built-in http.CrossOriginProtection:
app.Use(middleware.CSRF("https://example.com", "https://admin.example.com"))Pass trusted origins that are allowed to make state-changing requests. Requests from untrusted origins receive a 403 Forbidden Problem Details response.
For full control over the CSRF configuration:
csrf := http.NewCrossOriginProtection()csrf.AddTrustedOrigin("https://example.com")
app.Use(middleware.CSRFWith(csrf, problem.Problem{ Title: "CSRF Blocked", Detail: "Your custom error message", Status: http.StatusForbidden,}))Provide
Section titled “Provide”Injects values into the request context, making them available to downstream handlers:
app.Use(middleware.Provide(myKey, myValue))For dynamic values computed per request, use ProvideWith:
app.Use(middleware.ProvideWith(func(w http.ResponseWriter, r *http.Request) (context.Context, error) { user, err := authenticate(r) if err != nil { return nil, err } return context.WithValue(r.Context(), userKey, user), nil}))If the ProvideFunc returns an error, the request short-circuits and the error flows through normal error handling.
HTTP Adapter
Section titled “HTTP Adapter”Integrate standard Go func(http.Handler) http.Handler middleware with Cosmos:
import "github.com/rs/cors"
corsMiddleware := cors.New(cors.Options{ AllowedOrigins: []string{"https://example.com"},}).Handler
app.Use(middleware.HTTP(corsMiddleware))This adapter bridges the gap between Go’s standard middleware pattern and Cosmos’s error-returning handlers. It:
- Converts the Cosmos handler chain into a standard
http.Handler. - Applies the HTTP middleware.
- Captures any errors from downstream Cosmos handlers.
- Returns captured errors through the Cosmos middleware chain.
This means you can use any third-party Go HTTP middleware (CORS, rate limiting, etc.) without losing Cosmos error handling.
Writing Custom Middleware
Section titled “Writing Custom Middleware”A custom middleware is just a function that takes and returns a framework.Handler:
func Timing() framework.Middleware { return func(next framework.Handler) framework.Handler { return func(w http.ResponseWriter, r *http.Request) error { start := time.Now() err := next(w, r) slog.Info("request handled", "duration", time.Since(start)) return err } }}
app.Use(Timing())Middleware can short-circuit by returning an error without calling next:
func RequireAuth(token string) framework.Middleware { return func(next framework.Handler) framework.Handler { return func(w http.ResponseWriter, r *http.Request) error { if request.Header(r, "Authorization") != "Bearer "+token { return problem.Problem{ Title: "Unauthorized", Status: http.StatusUnauthorized, } } return next(w, r) } }}