Skip to main content
doc0blade47/honodocumentation home
Sign in

Request ID and Language

Last updated Oct 12, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

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().

OptionTypeDefault ValuePurpose
limitLengthnumber255Maximum allowed character length for incoming or generated request IDs.
headerNamestring'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 MemberType / SignatureTarget InterfacePurpose
RequestIdVariables{ requestId: string }ContextVariableMapDefines the mapping from the 'requestId' key to its string value type within application instances.
requestId(options?: RequestIdOptions) => MiddlewareHandlerN/AMiddleware 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 PropertyTypeDefault / ScopePurpose
headerAcceptHeaderRequiredSpecifies the HTTP header key to inspect (e.g., 'Accept-Language').
supportsstring[]RequiredArray of supported language codes or media types.
defaultstringRequiredFallback value returned when no entries match supported options.
match(accepts: Accept[], config: acceptsConfig) => stringOptionalCustom 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 NameImplementation FunctionSource LocationDescription
querystringdetectFromQuerysrc/middleware/language/language.ts:128-131Inspects URL query parameters using lookupQueryString.
cookiedetectFromCookiesrc/middleware/language/language.ts:136-139Retrieves cookie values using lookupCookie.
headerdetectFromHeadersrc/middleware/language/language.ts:144-162Parses Accept-Language headers via lookupFromHeaderKey.
pathdetectFromPathsrc/middleware/language/language.ts:167-172Extracts 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

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 PropertyDefault ValueDescription
sameSite'Strict'Restricts cross-site cookie transmission.
securetrueEnsures cookies are sent only over encrypted connections.
maxAge31536000 (365 * 24 * 60 * 60)Sets cookie lifetime to one year in seconds.
httpOnlytruePrevents 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

Was this page helpful?