Skip to content

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) Handler

Apply 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.

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 typeConversion
errorReturned as-is
stringWrapped in errors.New()
fmt.StringerUses String() method
io.ReaderReads all bytes
encoding.TextMarshalerUses MarshalText()
OtherFormatted 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")
}))

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

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.

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:

  1. Converts the Cosmos handler chain into a standard http.Handler.
  2. Applies the HTTP middleware.
  3. Captures any errors from downstream Cosmos handlers.
  4. 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.

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