Orientation Arc
Routing Algorithms
Rendering and JSX
Runtime Adapters
Middleware Ecosystem
Developer Helpers
System Utilities
The following files were used as context for generating this wiki page:
Middleware composition in Hono is a powerful architectural pattern that allows developers to chain independent request handlers into a unified pipeline. This mechanism enables cross-cutting concerns—such as security, caching, and compression—to be applied uniformly across routes or specific path patterns without polluting individual business-logic handlers.
At its core, composition follows a "decorator-like" pattern, where each middleware has the power to pre-process requests, execute subsequent handlers via a control-flow callback function, and post-process responses. This flow allows Hono to build complex stacks of behavior that are both modular and testable, keeping the application's request-response lifecycle clean.
The subsystem serves as the glue for Hono's extensibility. By leveraging the control-flow callback pattern, composition facilitates fine-grained control over execution flow. Developers can conditionally skip middleware, intercept early responses, or perform teardown logic after other handlers have completed. This design choice is fundamental to Hono's performance, as it avoids unnecessary overhead by resolving the execution order statically during router initialization and dynamically via the compose pipeline during runtime dispatch.
Hono employs a composition pipeline to sequence handlers for a matched route. The primary flow involves receiving a Request, routing it to identify a set of handlers, and then executing them sequentially. Each handler receives a Context object and a control-flow callback function. If a handler calls this function, control is passed to the next handler in the chain.
The mechanism relies on compose, which wraps an array of handlers. If a request matches multiple routes, the handlers are collapsed into a single execution stream. Crucially, if a handler does not call the provided callback and provides a response, the pipeline halts, and the final response is returned to the client, effectively short-circuiting the remaining middleware.
Sources: src/hono-base.ts:406-466
The combine module provides higher-order composition primitives: some, every, and except. These functions act as logic gates for middleware execution.
some: Executes a list of handlers until one completes successfully. It short-circuits execution if a handler reports success (typically by returning true).every: Requires all handlers to execute successfully. If any handler fails, the chain terminates.except: Conditional execution where middleware is applied unless the condition (either a path string or a function) is met.These primitives allow for sophisticated logic such as "skip authentication if the request comes from a local network" or "only run rate-limiting if the user lacks a valid bearer token."
Sources: src/middleware/combine/index.ts:38-164
Some middleware, like compress, act as stream-processing decorators. In this mechanism, the middleware calls the control-flow callback to allow the application logic to build a response, then captures that response to apply a CompressionStream.
This "wrap and transform" pattern is essential for modifying response bodies on the fly. The middleware must perform careful checks: it verifies that the Content-Type is compressible, checks for no-transform instructions in the Cache-Control header, and ensures that the response is not already compressed.
Important
The compress middleware converts strong ETags to weak ETags because the compression process alters the exact byte representation of the response body. This is a critical invariant to maintain cache consistency.
Sources: src/middleware/compress/index.ts:86-121
Middleware composition frequently uses the Context to share state across the pipeline. For example, the secure-headers middleware provides a NONCE generator. This function checks the context for an existing nonce; if not found, it generates a new one and stores it via ctx.set(). This state is then accessible to downstream view engines or template renderers.
The secureHeaders middleware also evaluates callbacks before calling the control-flow callback, ensuring that any dependencies required by the response (like a nonce) are populated before the main handler runs, yet the final header injection happens after the main handler produces the response.
Sources: src/middleware/secure-headers/secure-headers.ts:137-228
The cache middleware demonstrates a hybrid approach where it acts both as a guard and a pipeline modifier. It attempts a match against the Cache API before calling the control-flow callback. If a match exists, it terminates the request lifecycle early by returning the cached response.
If no match exists, it proceeds with the control-flow callback, waits for the application logic to populate the response, and then uses c.executionCtx.waitUntil() to asynchronously cache the response in the background. This ensures the request completion is not blocked by cache insertion.
Sources: src/middleware/cache/index.ts:138-180
When multiple middlewares or handlers are present, Hono manages execution priority. The router stores routes, and during dispatch, the matched route handlers are collected. The compose function ensures they are executed in the order of registration.
One critical safety guard exists in composition: isNextCalled in the some() implementation of the combine middleware. This variable prevents multiple invocations of the downstream pipeline, guaranteeing that if a condition is satisfied, the request lifecycle invariants are maintained.
Tip
Use combine primitives for declarative, clean composition rather than manually tracking state across multiple app.use() registrations.
Sources: src/middleware/combine/index.ts:39-68
This example demonstrates combining multiple security middlewares to protect an API endpoint.
import { Hono } from 'hono'
import { secureHeaders, NONCE } from 'hono/secure-headers'
import { some } from 'hono/combine'
import { bearerAuth } from 'hono/bearer-auth'
const app = new Hono()
// Apply security headers with a custom CSP nonce
app.use(secureHeaders({
contentSecurityPolicy: {
scriptSrc: [NONCE],
}
}))
// Use combined logic: skip authentication for specific paths
app.use('/api/*', some(
(c) => c.req.path.startsWith('/api/public'),
bearerAuth({ token: 'secret-token' })
))
app.get('/api/data', (c) => c.json({ data: 'secure' }))Sources: src/middleware/secure-headers/secure-headers.ts:184-190, src/middleware/combine/index.ts:38-68