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:
public typealias MiddlewareFunction = (Request, Responder) async throws -> ResponseIt 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
nextreturns, or skipnextentirely to short-circuit.
Registering middleware
Register middleware on the Application with use. The closure form is the common case:
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:
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:
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:
public protocol Middleware {
func respond(to request: Request, next: Responder) async throws -> Response
}A conforming type registers with the same use call:
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:
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:
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.principalError 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:
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:
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():
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.