PlumeKit Documentation

Middleware

Middleware wraps every request on its way to a route handler, and every response on the way back out. It is the place for cross-cutting concerns, such as logging, authentication, CSRF and method override, that should apply across routes rather than inside one handler.

Middleware is stored as concrete function values, so the stack runs identically on the native server and the Cloudflare Worker.

The middleware function

A middleware is a function that receives the request and the next responder in the chain, and returns a response:

SWIFT
public typealias MiddlewareFunction = (Request, Responder) async throws -> Response

It may do three things, in any combination:

  • Inspect or transform the request before calling next.
  • Call next(request) to continue down the chain and eventually reach the route handler.
  • Inspect or transform the response next returns, or skip next entirely to short-circuit.

Registering middleware

Register middleware on the Application with use. The closure form is the common case:

SWIFT
let app = Application()

// Log every request through the platform log seam
// (console.log on Workers, stdout natively).
app.use { request, next in
    let response = try await next(request)
    request.context.log("\(request.method.name) \(request.path) -> \(response.status)")
    return response
}

Short-circuiting

Sometimes a middleware should answer the request itself, for example to reject an unauthenticated caller. Return a response without calling next:

SWIFT
app.use { request, next in
    guard request.headers.first("x-api-key") == "let-me-in" else {
        return .text("unauthorized", status: 401)
    }
    return try await next(request)
}

Rewriting the request

Request is a value type, so mutations are local until you forward them. To rewrite the request, make a mutable copy and pass it on:

SWIFT
app.use { request, next in
    var forwarded = request
    forwarded.headers.set("x-request-id", makeID())
    return try await next(forwarded)
}

The protocol form

For reusable middleware with configuration or state, conform a concrete type to Middleware:

SWIFT
public protocol Middleware {
    func respond(to request: Request, next: Responder) async throws -> Response
}

A conforming type registers with the same use call:

SWIFT
struct RequireHTTPS: Middleware {
    func respond(to request: Request, next: Responder) async throws -> Response {
        if request.headers.first("x-forwarded-proto") == "http" {
            return .redirect(to: "https://…", status: 308)
        }
        return try await next(request)
    }
}

app.use(RequireHTTPS())

use(_:) adapts the value to a MiddlewareFunction at registration time.

Ordering

Registration order is nesting order. The first middleware registered is the outermost: it runs first on the way in and last on the way out. Given three registrations:

SWIFT
app.use(A)
app.use(B)
app.use(C)

A request flows from A to B to C to the handler, and the response returns through C, B and A in that order.

Register broad, early-exit concerns (logging, method override, auth) before narrower ones. A typical order:

SWIFT
app.use(loggingMiddleware)      // outermost: sees final status
app.use(methodOverride())       // rewrite POST → PUT/PATCH/DELETE before routing
app.use(csrfProtection())       // reject unsafe requests without a valid token
app.use(identityMiddleware(sessions))   // resolve request.principal

Error handling

Handlers and middleware are async throws. A thrown error that no middleware catches propagates to the framework, which returns a 500 Internal Server Error.

To translate errors into specific responses, catch them in a middleware:

SWIFT
app.use { request, next in
    do {
        return try await next(request)
    } catch is NotFound {
        return .status(404)
    }
}

Scoping middleware

Middleware registered with use runs for every request. To apply middleware to a subset of routes, use a route group (see Routing).

Alternatively, gate on the request inside the middleware:

SWIFT
app.use { request, next in
    guard request.path.hasPrefix("/admin") else { return try await next(request) }
    // …admin-only checks…
    return try await next(request)
}

Built-in middleware

PlumeKit ships several middleware factories in PlumeCore. Register the ones you need in buildApp():

SWIFT
app.use(methodOverride())
app.use(csrfProtection())
app.use(identityMiddleware(sessions))

methodOverride()

Rewrites a POST into PUT, PATCH or DELETE when the body carries a _method field (or an X-HTTP-Method-Override header). HTML forms can only issue GET and POST, so this lets a form drive a resourceful route. It runs before routing, so the overridden method is what matches. See Forms.

csrfProtection(secretName:)

Rejects unsafe requests (POST, PUT, PATCH, DELETE) that lack a valid CSRF token. The token is validated timing-safely against a signing secret read from the secrets binding (default "CSRF_SECRET"). JSON-body requests and bearer-token requests are exempt, since they are not exposed to CSRF; it guards ambient-credential form and multipart submissions. See Forms.

identityMiddleware(_:cookieName:)

Resolves the authenticated identity from a signed cookie session or an Authorization: Bearer token and sets request.principal, so downstream handlers and policies can read currentUser. See Auth.