howdy/middleware

Middleware runs around a handler. It can reject a request before the handler runs, change the context on the way in, or change the response on the way out.

pub fn require_api_key(ctx: Context, next: Next) {
  case request.get_header(ctx.request, "x-api-key") {
    Ok("secret") -> next(ctx)
    _ -> service.error_response(ctx, service.Unauthorized)
  }
}

Apply it at three levels:

howdy.new() |> howdy.middleware(request_logger)              // every request
controller.new("user") |> controller.middleware(require_api_key) // every route
controller.post("/", create |> middleware.wrap(rate_limit))   // one route

App middleware wraps controller middleware, which wraps route middleware, which wraps the handler. Within a level the first added is the outermost.

Types

Values

pub fn rescue(
  ctx: context.Context(Nil),
  next: fn(context.Context(Nil)) -> response.Response(
    content.Content,
  ),
) -> response.Response(content.Content)

Answer a crashed handler with a 500 in the same shape as every other error, instead of dropping the connection.

Without it the server still replies 500 to a crash, but with an empty body and, on HTTP/1, a closed connection, and the app gets no say. With it the crash is logged with its stack trace and the current trace id, the response is service.Internal’s JSON, and the connection stays open.

howdy.new() |> howdy.middleware(middleware.rescue)

Add it first, so it wraps the rest of the app’s middleware too.

pub fn wrap(
  handler: fn(context.Context(Nil)) -> response.Response(
    content.Content,
  ),
  middleware: fn(
    context.Context(Nil),
    fn(context.Context(Nil)) -> response.Response(content.Content),
  ) -> response.Response(content.Content),
) -> fn(context.Context(Nil)) -> response.Response(
  content.Content,
)

Wrap a handler in one middleware.

pub fn wrap_all(
  handler: fn(context.Context(Nil)) -> response.Response(
    content.Content,
  ),
  middleware: List(
    fn(
      context.Context(Nil),
      fn(context.Context(Nil)) -> response.Response(
        content.Content,
      ),
    ) -> response.Response(content.Content),
  ),
) -> fn(context.Context(Nil)) -> response.Response(
  content.Content,
)

Wrap a handler in several middleware. The first in the list is the outermost.

✨ Search Document