Relevant source files
The following files were used as context for generating this wiki page:
- src/middleware/request-id/request-id.ts
- src/middleware/secure-headers/secure-headers.ts
- src/middleware/language/language.ts
- src/context.ts
- src/adapter/aws-lambda/handler.ts
- src/middleware/request-id/index.ts
- src/helper/route/index.ts
- src/middleware/logger/index.ts
- src/middleware/language/index.ts
- src/request.ts
- src/middleware/ip-restriction/index.ts
- src/middleware/cache/index.ts
- src/middleware/etag/index.ts
- src/middleware/jsx-renderer/index.ts
- src/middleware/method-override/index.ts
- src/helper/accepts/accepts.ts
- src/middleware/timing/timing.ts
- src/adapter/cloudflare-pages/handler.ts
- src/middleware/context-storage/index.ts
- src/helper/streaming/text.ts
- src/helper/ssg/middleware.ts
- src/hono-base.ts
- src/adapter/vercel/conninfo.ts
- src/adapter/lambda-edge/conninfo.ts
- src/helper/adapter/index.ts
- src/utils/headers.ts
- package.json
- src/helper/accepts/index.ts
- runtime-tests/workerd/index.ts
- src/middleware/trailing-slash/index.ts
Overview
Hono provides specialized middleware and utilities for tracking requests and managing client localization across web applications. The request ID middleware assigns or propagates unique correlation identifiers via configurable headers and generator functions, integrating directly with TypeScript type definitions through declaration merging. For localization, language detection strategies parse quality-weighted Accept-Language headers, query parameters, cookies, and URL paths, normalizing locale tags against supported options and persisting selections to optimize downstream handling.
Sources: src/middleware/request-id/request-id.ts:9-56, src/middleware/request-id/index.ts:5-7, src/middleware/language/language.ts:13-172, src/middleware/language/index.ts:10-12
Request identifier generation and propagation
Middleware configuration and options
The requestId middleware factory accepts an optional RequestIdOptions configuration object defining constraints for request tracking. If omitted, the middleware defaults to a maximum length of 255 characters, uses X-Request-Id as the header name, and generates identifiers using crypto.randomUUID().
| Option | Type | Default Value | Purpose |
|---|---|---|---|
limitLength | number | 255 | Maximum allowed character length for incoming or generated request IDs. |
headerName | string | 'X-Request-Id' | HTTP header name used to read incoming request IDs and inject outgoing response IDs. |
generator | (c: Context) => string | () => crypto.randomUUID() | Custom function invoked to produce a new identifier when validation fails or no header is present. |
Sources: src/middleware/request-id/request-id.ts:13-17, src/middleware/request-id/request-id.ts:41-45
Generation and validation flow
When an incoming request hits the middleware, it checks whether headerName is configured. If headerName is provided, the middleware reads the corresponding header from the request object. If headerName is an empty string, c.req.header() evaluates to an undefined or unqueried state.
Sources: src/middleware/request-id/request-id.ts:46-58
Warning
Incoming request identifiers must pass strict validation checks before being accepted. If an incoming header value exceeds limitLength or contains characters outside alphanumeric values, hyphens, and equals signs (/[^\w\-=]/), the middleware discards the header value and invokes the generator function instead.
Sources: src/middleware/request-id/request-id.ts:49-51
Context variable typing for request identifiers
Type definition and declaration merging
The request ID middleware exposes the RequestIdVariables type definition, which defines the variable map shape for context storage. To provide seamless type safety when accessing request identifiers via c.get('requestId'), the module extends Hono's global ContextVariableMap interface through declaration merging.
export type RequestIdVariables = {
requestId: string
}Sources: src/middleware/request-id/request-id.ts:9-11
import type { RequestIdVariables } from './request-id'
export type { RequestIdVariables }
export { requestId } from './request-id'
declare module '../..' {
interface ContextVariableMap extends RequestIdVariables {}
}Sources: src/middleware/request-id/index.ts:1-7
| Exported Member | Type / Signature | Target Interface | Purpose |
|---|---|---|---|
RequestIdVariables | { requestId: string } | ContextVariableMap | Defines the mapping from the 'requestId' key to its string value type within application instances. |
requestId | (options?: RequestIdOptions) => MiddlewareHandler | N/A | Middleware factory that assigns the generated ID to requestId via c.set(). |
Sources: src/middleware/request-id/request-id.ts:9-11, src/middleware/request-id/request-id.ts:41-45, src/middleware/request-id/index.ts:1-7
Context retrieval and instantiation
Developers integrate the typed variables by importing RequestIdVariables and passing it to the generic Hono application constructor. Once registered, handlers can retrieve the identifier using c.get('requestId') with full type inference.
type Variables = RequestIdVariables
const app = new Hono<{ Variables: Variables }>()
app.use(requestId())
app.get('/', (c) => {
console.log(c.get('requestId'))
return c.text('Hello World!')
})Sources: src/middleware/request-id/request-id.ts:31-39
Note
When importing from hono/request-id, declaration merging automatically augments the global ContextVariableMap so that manual generic binding is optional if utilizing global type environments, while explicit generic parameterization ensures strict scoped typing in modular applications.
Sources: src/middleware/request-id/index.ts:5-7
Accept-Language header parsing and quality weighting
Parsing and quality score extraction
The parseAcceptLanguage function extracts language tags and their associated quality values (q) from raw Accept-Language headers. It delegates the underlying string decomposition to the utility function parseAccept, then maps each returned record to an array of objects containing the language type (lang) and quality weighting (q).
export function parseAcceptLanguage(header: string): Array<{ lang: string; q: number }> {
return parseAccept(header).map(({ type, q }) => ({ lang: type, q }))
}Sources: src/middleware/language/language.ts:74-76
When matching headers explicitly via helpers, the accepts function retrieves the header specified in configuration options and processes the values. If the header is missing, it falls back to the default configured string.
export const accepts = (c: Context, options: acceptsOptions): string => {
const acceptHeader = c.req.header(options.header)
if (!acceptHeader) {
return options.default
}
const accepts = parseAccept(acceptHeader)
const match = options.match || defaultMatch
return match(accepts, options)
}Sources: src/helper/accepts/accepts.ts:40-49
Quality weighting and default matching
The defaultMatch function sorts parsed accept objects in descending order by their quality weighting score (q) and returns the first language tag that exists within the supported languages array. If no matches are found, it yields the configured default string.
export const defaultMatch = (accepts: Accept[], config: acceptsConfig): string => {
const { supports, default: defaultSupport } = config
const accept = accepts.sort((a, b) => b.q - a.q).find((accept) => supports.includes(accept.type))
return accept ? accept.type : defaultSupport
}Sources: src/helper/accepts/accepts.ts:21-25
| Configuration Property | Type | Default / Scope | Purpose |
|---|---|---|---|
header | AcceptHeader | Required | Specifies the HTTP header key to inspect (e.g., 'Accept-Language'). |
supports | string[] | Required | Array of supported language codes or media types. |
default | string | Required | Fallback value returned when no entries match supported options. |
match | (accepts: Accept[], config: acceptsConfig) => string | Optional | Custom matching strategy function replacing defaultMatch. |
Sources: src/helper/accepts/accepts.ts:11-19
Warning
The defaultMatch algorithm strictly iterates through sorted quality values from highest to lowest. If a client provides multiple supported languages with identical quality scores, the order in which they appear in the raw header string dictates precedence based on the underlying parser's stability.
Sources: src/helper/accepts/accepts.ts:21-23
Language detector strategy and lookup chain
Detection strategies and mapping
The language module supports four distinct detection strategies encapsulated within the DetectorType union: 'path', 'querystring', 'cookie', and 'header'. Each strategy extracts a raw string candidate from different parts of the incoming HTTP request context, which is then processed through normalizeLanguage against the configured supportedLanguages. The detectors mapping object binds these strategy identifiers to their respective implementation functions.
| Strategy Name | Implementation Function | Source Location | Description |
|---|---|---|---|
querystring | detectFromQuery | src/middleware/language/language.ts:128-131 | Inspects URL query parameters using lookupQueryString. |
cookie | detectFromCookie | src/middleware/language/language.ts:136-139 | Retrieves cookie values using lookupCookie. |
header | detectFromHeader | src/middleware/language/language.ts:144-162 | Parses Accept-Language headers via lookupFromHeaderKey. |
path | detectFromPath | src/middleware/language/language.ts:167-172 | Extracts path segments from the URL path at lookupFromPathIndex. |
Sources: src/middleware/language/language.ts:10-10, src/middleware/language/language.ts:128-182
Normalization and RFC 4647 lookup
The normalizeLanguage function validates and cleanses raw language strings using progressive truncation adhering to RFC 4647 lookup rules. When ignoreCase is enabled, candidate strings and supported entries are lowercased for comparison. If an exact match fails, the function splits the language code by hyphens and progressively truncates segments from right to left until a matching prefix is found in supportedLanguages.
export const normalizeLanguage = (
lang: string | null | undefined,
options: DetectorOptions
): string | undefined => {
if (!lang) {
return undefined
}
try {
let normalizedLang = lang.trim()
if (options.convertDetectedLanguage) {
normalizedLang = options.convertDetectedLanguage(normalizedLang)
}
const compLang = options.ignoreCase ? normalizedLang.toLowerCase() : normalizedLang
const compSupported = options.supportedLanguages.map((l) =>
options.ignoreCase ? l.toLowerCase() : l
)
// Exact match
const exactIndex = compSupported.indexOf(compLang)
if (exactIndex !== -1) {
return options.supportedLanguages[exactIndex]
}
// Progressive truncation (RFC 4647 Lookup)
const parts = compLang.split('-')
for (let i = parts.length - 1; i > 0; i--) {
const candidate = parts.slice(0, i).join('-')
const prefixIndex = compSupported.indexOf(candidate)
if (prefixIndex !== -1) {
return options.supportedLanguages[prefixIndex]
}
}
return undefined
} catch {
return undefined
}
}Sources: src/middleware/language/language.ts:84-123
Warning
If a custom transformation function is provided via convertDetectedLanguage, it executes before normalization and case-folding, allowing pre-processing of non-standard locale tokens.
Sources: src/middleware/language/language.ts:94-96
Validation of detector options
Before runtime execution, validateOptions inspects the active DetectorOptions configuration to ensure consistency. It verifies that fallbackLanguage exists within the supportedLanguages collection, that lookupFromPathIndex is non-negative, and that all entries in the order array correspond to valid keys within the detectors map.
export function validateOptions(options: DetectorOptions): void {
if (!options.supportedLanguages.includes(options.fallbackLanguage)) {
throw new Error('Fallback language must be included in supported languages')
}
if (options.lookupFromPathIndex < 0) {
throw new Error('Path index must be non-negative')
}
if (!options.order.every((detector) => Object.keys(detectors).includes(detector))) {
throw new Error('Invalid detector type in order array')
}
}Sources: src/middleware/language/language.ts:195-207
Locale persistence and response caching
Cookie-based caching strategies
The language detection middleware manages detected locale persistence through the cacheLanguage function, which serializes resolved language codes into HTTP cookies. Caching behavior is governed by the caches option array and the default configuration settings.
export const DEFAULT_OPTIONS: DetectorOptions = {
order: ['querystring', 'cookie', 'header'],
lookupQueryString: 'lang',
lookupCookie: 'language',
lookupFromHeaderKey: 'accept-language',
lookupFromPathIndex: 0,
caches: ['cookie'],
ignoreCase: true,
fallbackLanguage: 'en',
supportedLanguages: ['en'],
cookieOptions: {
sameSite: 'Strict',
secure: true,
maxAge: 365 * 24 * 60 * 60,
httpOnly: true,
},
debug: false,
}Sources: src/middleware/language/language.ts:51-68
The cacheLanguage utility inspects whether 'cookie' is present in options.caches before invoking setCookie with the configured lookupCookie identifier and cookieOptions.
function cacheLanguage(c: Context, language: string, options: DetectorOptions): void {
if (!Array.isArray(options.caches) || !options.caches.includes('cookie')) {
return
}
try {
setCookie(c, options.lookupCookie, language, options.cookieOptions)
} catch (error) {
if (options.debug) {
console.error('Failed to cache language:', error)
}
}
}Sources: src/middleware/language/language.ts:212-224
| Option Property | Default Value | Description |
|---|---|---|
sameSite | 'Strict' | Restricts cross-site cookie transmission. |
secure | true | Ensures cookies are sent only over encrypted connections. |
maxAge | 31536000 (365 * 24 * 60 * 60) | Sets cookie lifetime to one year in seconds. |
httpOnly | true | Prevents client-side script access to the cookie. |
Sources: src/middleware/language/language.ts:61-66
Caution
If cookie serialization fails during cacheLanguage execution, the error is swallowed unless options.debug is enabled, in which case it prints to console.error.
Sources: src/middleware/language/language.ts:217-223
Response mutation and headers manipulation
Response header modification and persistence are handled across various platform adapters and utility layers. Secure headers, timing metrics, and response cookies apply mutations directly to response headers or result objects. The secure headers middleware mutates response headers via setHeaders, which assigns security policies directly to ctx.res.headers.
function setHeaders(ctx: Context, headersToSet: [string, string][]) {
headersToSet.forEach(([header, value]) => {
ctx.res.headers.set(header, value)
})
}Sources: src/middleware/secure-headers/secure-headers.ts:330-334
Similarly, the server-timing middleware appends execution metrics and cross-origin timing allowances directly to the response headers collection.
c.res.headers.append('Server-Timing', headers.join(','))
const crossOrigin =
typeof options.crossOrigin === 'function' ? options.crossOrigin(c) : options.crossOrigin
if (crossOrigin) {
c.res.headers.append(
'Timing-Allow-Origin',
typeof crossOrigin === 'string' ? crossOrigin : '*'
)Sources: src/middleware/timing/timing.ts:113-122
Platform-specific result formatting
When handling requests under serverless or edge environments, response cookies and headers require explicit mapping to platform result objects. AWS Lambda processors handle cookie persistence via setCookiesToResult implementations across v1, v2, and ALB event structures, assigning serialized cookie arrays to multiValueHeaders['set-cookie'] or result.cookies.
protected setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void {
result.cookies = cookies
}Sources: src/adapter/aws-lambda/handler.ts:423-425
Cloudflare Pages middleware constructs a Web Standard Context instance wrapping the execution event request and environment bindings, parsing exceptions and HTTP exceptions into finalized Response instances.
return async (executionCtx) => {
const context = new Context(executionCtx.request, {
env: { ...executionCtx.env, eventContext: executionCtx },
executionCtx,
})
let response: Response | void = undefined
try {
response = await middleware(context, async () => {
try {
context.res = await executionCtx.next()
} catch (error) {
if (error instanceof Error) {
context.error = error
} else {
throw error
}
}
})
} catch (error) {
if (error instanceof Error) {
context.error = error
} else {
throw error
}
}
if (response) {
return response
}
if (context.error instanceof HTTPException) {
return context.error.getResponse()
}
if (context.error) {
throw context.error
}
return context.res
}Sources: src/adapter/cloudflare-pages/handler.ts:60-101
Related
On This Page
OverviewRequest identifier generation and propagationMiddleware configuration and optionsGeneration and validation flowContext variable typing for request identifiersType definition and declaration mergingContext retrieval and instantiationAccept-Language header parsing and quality weightingParsing and quality score extractionQuality weighting and default matchingLanguage detector strategy and lookup chainDetection strategies and mappingNormalization and RFC 4647 lookupValidation of detector optionsLocale persistence and response cachingCookie-based caching strategiesResponse mutation and headers manipulationPlatform-specific result formattingRelated