Relevant source files
The following files were used as context for generating this wiki page:
- src/context.ts
- src/request.ts
- src/types.ts
- src/adapter/aws-lambda/handler.ts
- src/helper/streaming/sse.ts
- src/helper/route/index.ts
- src/middleware/jsx-renderer/index.ts
- src/hono-base.ts
- src/middleware/request-id/index.ts
- src/helper/streaming/stream.ts
- src/helper/streaming/text.ts
- src/middleware/timing/index.ts
- src/adapter/cloudflare-pages/handler.ts
- src/adapter/aws-lambda/types.ts
- src/middleware/context-storage/index.ts
- src/jsx/context.ts
- src/helper/adapter/index.ts
- src/adapter/bun/server.ts
- src/adapter/lambda-edge/conninfo.ts
- src/middleware/language/index.ts
- src/middleware/secure-headers/index.ts
- src/adapter/netlify/handler.ts
- src/helper/testing/index.ts
- src/middleware/jwt/index.ts
- src/middleware/ip-restriction/index.ts
Overview
The Context class manages the complete execution lifecycle for incoming HTTP requests src/context.ts. It encapsulates routing parameters, request headers, environment bindings, and response generation utilities within a unified interface passed to handlers and middleware src/context.ts, src/request.ts.
Sources: src/context.ts:293-361
Sources: src/context.ts:315-434, src/request.ts:94-198
Context Lifecycle and Internal State
Constructor Initialization and Request Wrapping
The Context class constructor takes a raw Web Standard Request object and an optional ContextOptions record src/context.ts:352-361. It retains the underlying request as #rawRequest and initializes instance parameters such as execution contexts, environment bindings, not-found handlers, path specifications, and routing match results if provided src/context.ts:300-360.
Sources: src/context.ts:352-361
Request parsing is deferred via the .req getter property, which lazily instantiates a HonoRequest wrapper around #rawRequest, #path, and #matchResult upon first access src/context.ts:366-369. Similarly, Cloudflare Pages middleware integration constructs a Context instance by passing the event execution context alongside extended bindings src/adapter/cloudflare-pages/handler.ts:60-64.
Sources: src/context.ts:366-369
Sources: src/adapter/cloudflare-pages/handler.ts:60-64
constructor(req: Request, options?: ContextOptions<E>) {
this.#rawRequest = req
if (options) {
this.#executionCtx = options.executionCtx
this.env = options.env
this.#notFoundHandler = options.notFoundHandler
this.#path = options.path
this.#matchResult = options.matchResult
}
}Sources: src/context.ts:352-361
return async (executionCtx) => {
const context = new Context(executionCtx.request, {
env: { ...executionCtx.env, eventContext: executionCtx },
executionCtx,
})Sources: src/adapter/cloudflare-pages/handler.ts:60-64
Execution State and Response Tracking
An active Context instance maintains internal tracking flags and response fields throughout the handler execution cycle src/context.ts:315-345. The finalized boolean flag marks whether a response has been committed or set src/context.ts:317. Platform-specific capabilities such as Cloudflare Workers event bindings are accessed through strict getters: the event getter verifies the presence of respondWith on the execution context, while the executionCtx getter validates standard platform contexts, throwing errors if accessed outside a supported environment src/context.ts:377-397.
Sources: src/context.ts:315-345
Sources: src/context.ts:317
Sources: src/context.ts:377-397
get res(): Response {
return (this.#res ||= createResponseInstance(null, {
headers: (this.#preparedHeaders ??= new Headers()),
}))
}
/**
* Sets the Response object for the current request.
*
* @param _res - The Response object to set.
*/
set res(_res: Response | undefined) {
if (this.#res && _res) {
_res = createResponseInstance(_res.body, _res)
for (const [k, v] of this.#res.headers.entries()) {
if (k === 'content-type') {
continue
}
if (k === 'set-cookie') {
const cookies = this.#res.headers.getSetCookie()
_res.headers.delete('set-cookie')
for (const cookie of cookies) {
_res.headers.append('set-cookie', cookie)
}
} else {
_res.headers.set(k, v)
}
}
}
this.#res = _res
this.finalized = true
}Sources: src/context.ts:403-434
Note
Setting the response property (c.res = ...) automatically marks the context as finalized and copies existing header entries—such as preserving set-cookie arrays and skipping duplicate content types—into the incoming response instance src/context.ts:414-434.
Sources: src/context.ts:414-434
The table below details the internal state properties managed directly within the Context class instance.
| Field / Property | Internal Variable | Type | Description |
|---|---|---|---|
req | #req, #rawRequest | HonoRequest | undefined, Request | Lazily wrapped request accessor and underlying fetch request src/context.ts:300-301, 366-369 |
env | env | E['Bindings'] | Environment variables, secrets, and bindings object src/context.ts:315 |
error | error | Error | undefined | Captured error object thrown during middleware or handler execution src/context.ts:333 |
event | #executionCtx | FetchEventLike | FetchEvent instance supporting respondWith src/context.ts:336, 377-383 |
executionCtx | #executionCtx | ExecutionContext | Platform execution context supporting methods like waitUntil src/context.ts:336, 391-397 |
res | #res, #preparedHeaders | Response, Headers | undefined | Current response object and prepared headers buffer src/context.ts:337, 341, 403-407 |
finalized | finalized | boolean | Flag indicating whether the response has been sent or locked src/context.ts:317 |
Sources: src/context.ts:300-434
Request Parameter and Header Accessors
HonoRequest Initialization and Parameter Retrieval
The Context instance exposes incoming request properties through its .req property src/context.ts:366-369. Accessing c.req triggers the lazy instantiation of a HonoRequest wrapper, combining the raw underlying Request object with route path variables and match results src/context.ts:366-369.
Sources: src/context.ts:366-369
get req(): HonoRequest<P, I['out']> {
this.#req ??= new HonoRequest(this.#rawRequest, this.#path, this.#matchResult)
return this.#req
}Sources: src/context.ts:366-369
Note
The HonoRequest instance is initialized precisely on first access (this.#req ??= ...), avoiding parsing overhead for requests that do not inspect parameters, headers, or query strings src/context.ts:366-369.
Sources: src/context.ts:366-369
Variable Storage and Context Typing
Context Variable Map and Generic Constraints
The Context class manages scoped variables across middleware and request handlers via a private #var instance of JavaScript's Map src/context.ts:316. These variables are typed through the generic E constraint parameter extending Env, allowing applications to define strict typing for key-value pairs shared during request execution src/context.ts:293-295.
Sources: src/context.ts:316
Sources: src/context.ts:293-295
#var: Map<unknown, unknown> | undefinedSources: src/context.ts:316
Note
The internal #var map is lazily initialized on first write or read operation, preventing unnecessary allocation for request contexts that do not utilize scoped variables src/context.ts:316.
Sources: src/context.ts:316
Response Construction and Helpers
Response Instance Creation and Default Types
Response generation relies on underlying Web Standard constructor wrappers src/context.ts:288-291. The helper createResponseInstance wraps new Response(body, init), providing the foundation for text, stream, and binary outputs src/context.ts:288-291. Additionally, TEXT_PLAIN defines the default UTF-8 plain text content type string src/context.ts:279, while setDefaultContentType merges content type headers with existing header records src/context.ts:281-286.
Sources: src/context.ts:288-291
Sources: src/context.ts:279, src/context.ts:281-286
export const TEXT_PLAIN = 'text/plain; charset=UTF-8'
const setDefaultContentType = (contentType: string, headers?: HeaderRecord): HeaderRecord => {
return {
'Content-Type': contentType,
...headers,
}
}
const createResponseInstance = (
body?: BodyInit | null | undefined,
init?: globalThis.ResponseInit
): Response => new Response(body, init)Sources: src/context.ts:279-291
Warning
Modifying headers after a response has been finalized forces Hono to instantiate a new underlying Response object to prevent mutation errors on immutable response streams src/context.ts:516-518.
Sources: src/context.ts:516-518
Stream SSE Execution Walkthrough
The streamSSE helper orchestrates Server-Sent Events by initializing a transform stream, invoking c.header to configure transport headers, and utilizing createResponseInstance to generate the final response src/helper/streaming/sse.ts:72-100, src/context.ts:288-291.
Sources: src/helper/streaming/sse.ts:72-100, src/context.ts:288-291
streamSSEinitializes aTransformStreamand wraps it in anSSEStreamingApiinstance src/helper/streaming/sse.ts:77-78.isOldBunVersion()checks for legacy runtime behavior, binding an abort listener toc.req.raw.signalif necessary src/helper/streaming/sse.ts:80-87.contextStash.setcaches the context against the response readable stream to prevent premature garbage collection in Bun src/helper/streaming/sse.ts:89-90.c.headersets required transport and protocol headers includingTransfer-Encoding,Content-Type,Cache-Control, andConnectionsrc/helper/streaming/sse.ts:92-95, src/context.ts:515-527.runexecutes the user callback with error handling and automatic stream closure src/helper/streaming/sse.ts:47-68, 97.createResponseInstanceis invoked through response helpers to construct the final Response wrappingstream.responseReadablesrc/helper/streaming/sse.ts:99, src/context.ts:288-291.
Sources: src/helper/streaming/sse.ts:77-78
Sources: src/helper/streaming/sse.ts:80-87
Sources: src/helper/streaming/sse.ts:89-90
Sources: src/helper/streaming/sse.ts:92-95, src/context.ts:515-527
Sources: src/helper/streaming/sse.ts:47-68, src/helper/streaming/sse.ts:97
Sources: src/helper/streaming/sse.ts:99, src/context.ts:288-291
export const streamSSE = (
c: Context,
cb: (stream: SSEStreamingApi) => Promise<void>,
onError?: (e: Error, stream: SSEStreamingApi) => Promise<void>
): Response => {
const { readable, writable } = new TransformStream()
const stream = new SSEStreamingApi(writable, readable)
// Until Bun v1.1.27, Bun didn't call cancel() on the ReadableStream for Response objects from Bun.serve()
if (isOldBunVersion()) {
c.req.raw.signal.addEventListener('abort', () => {
if (!stream.closed) {
stream.abort()
}
})
}
// in bun, `c` is destroyed when the request is returned, so hold it until the end of streaming
contextStash.set(stream.responseReadable, c)
c.header('Transfer-Encoding', 'chunked')
c.header('Content-Type', 'text/event-stream')
c.header('Cache-Control', 'no-cache')
c.header('Connection', 'keep-alive')
run(stream, cb, onError)
return c.newResponse(stream.responseReadable)
}Sources: src/helper/streaming/sse.ts:72-100
Sources: src/helper/streaming/sse.ts:72-100, src/context.ts:288-291
Note
An internal contextStash WeakMap holds a reference to the active Context keyed by stream.responseReadable, ensuring the request context remains accessible throughout long-lived streaming connections src/helper/streaming/sse.ts:70-90.
Sources: src/helper/streaming/sse.ts:70-90
Adapter Bindings and Environment Access
Runtime Identification and Environment Resolution
Hono resolves environment bindings and execution contexts across heterogeneous JavaScript runtimes by combining runtime detection helpers with platform-specific adapter handlers. The getRuntimeKey() utility inspects globalThis properties and user agent strings to classify the active platform into one of the supported runtime identifiers src/helper/adapter/index.ts:50-84. Once identified, the env() helper dynamically dispatches to the correct environment provider, returning process environment variables or platform-native binding stores src/helper/adapter/index.ts:10-41.
Sources: src/helper/adapter/index.ts:50-84
Sources: src/helper/adapter/index.ts:10-41
| Runtime Key | Detection Mechanism | Environment Source |
|---|---|---|
bun | navigator.userAgent starts with Bun | process.env |
node | navigator.userAgent starts with Node.js or process.release.name === 'node' | process.env |
deno | navigator.userAgent starts with Deno | Deno.env.toObject() |
workerd | navigator.userAgent starts with Cloudflare-Workers | c.env |
edge-light | globalThis.EdgeRuntime is a string | process.env |
fastly | globalThis.fastly !== undefined | {} |
other | Fallback when no platform signature matches | {} |
Sources: src/helper/adapter/index.ts:8-48
export type Runtime = 'node' | 'deno' | 'bun' | 'workerd' | 'fastly' | 'edge-light' | 'other'
export const env = <
T extends Record<string, unknown>,
C extends Context = Context<{
Bindings: T
}>,
>(
c: T extends Record<string, unknown> ? Context : C,
runtime?: Runtime
): T & C['env'] => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const global = globalThis as any
const globalEnv = global?.process?.env as T
runtime ??= getRuntimeKey()
const runtimeEnvHandlers: Record<string, () => T> = {
bun: () => globalEnv,
node: () => globalEnv,
'edge-light': () => globalEnv,
deno: () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return Deno.env.toObject() as T
},
workerd: () => c.env,
// On Fastly Compute, you can use the ConfigStore to manage user-defined data.
fastly: () => ({}) as T,
other: () => ({}) as T,
}
return runtimeEnvHandlers[runtime]()
}
export const knownUserAgents: Partial<Record<Runtime, string>> = {
deno: 'Deno',
bun: 'Bun',
workerd: 'Cloudflare-Workers',
node: 'Node.js',
}Sources: src/helper/adapter/index.ts:8-48
Note
The env() helper requires an explicit runtime parameter or falls back to getRuntimeKey(), which inspects navigator.userAgent before checking runtime-specific global objects src/helper/adapter/index.ts:17-23.
Sources: src/helper/adapter/index.ts:17-23
Platform Adapter Handlers and Execution Contexts
Cloudflare Pages, AWS Lambda, and Netlify integrate with Hono by mapping native platform events and execution contexts into the standard app.fetch() signature src/adapter/aws-lambda/handler.ts:268-272, src/adapter/cloudflare-pages/handler.ts:37-45, src/adapter/netlify/handler.ts:8. Cloudflare Pages passes eventContext.request, environment bindings augmented with eventContext, and an execution options object containing waitUntil and passThroughOnException callbacks src/adapter/cloudflare-pages/handler.ts:37-45.
Sources: src/adapter/aws-lambda/handler.ts:268-272, src/adapter/cloudflare-pages/handler.ts:37-45, src/adapter/netlify/handler.ts:8
Sources: src/adapter/cloudflare-pages/handler.ts:37-45
export const handle =
<E extends Env = Env, S extends Schema = BlankSchema, BasePath extends string = '/'>(
app: Hono<E, S, BasePath>
): PagesFunction<E['Bindings']> =>
(eventContext) => {
return app.fetch(
eventContext.request,
{ ...eventContext.env, eventContext },
{
waitUntil: eventContext.waitUntil,
passThroughOnException: eventContext.passThroughOnException,
props: {},
}
)
}Sources: src/adapter/cloudflare-pages/handler.ts:32-46
AWS Lambda adapters process incoming API Gateway or ALB events via event processors (EventV1Processor and EventV2Processor), converting event payloads into standard Request instances and passing the raw event, requestContext, and lambdaContext through to app.fetch() src/adapter/aws-lambda/handler.ts:252-272. Similarly, Netlify's adapter delegates incoming requests directly to app.fetch() accompanied by the Netlify execution context object src/adapter/netlify/handler.ts:4-10. Connection info helpers, such as the Lambda@Edge implementation, extract client IP addresses directly from nested event records stored within c.env src/adapter/lambda-edge/conninfo.ts:11-14.
Sources: src/adapter/aws-lambda/handler.ts:252-272
Sources: src/adapter/netlify/handler.ts:4-10
Sources: src/adapter/lambda-edge/conninfo.ts:11-14
return async (event, lambdaContext?) => {
const processor = getProcessor(event)
let req, requestContext
try {
req = processor.createRequest(event)
requestContext = getRequestContext(event)
} catch (error) {
console.error('Error processing request:', error)
const errorResponse =
error instanceof TypeError
? new Response('Invalid request', { status: 400 })
: new Response('Internal Server Error', { status: 500 })
return processor.createResult(event, errorResponse, { isContentTypeBinary })
}
const res = await app.fetch(req, {
event,
requestContext,
lambdaContext,
})
return processor.createResult(event, res, { isContentTypeBinary })
}
}Sources: src/adapter/aws-lambda/handler.ts:252-276
Warning
Accessing c.event or c.executionCtx on platforms that do not supply a matching execution context object will throw an explicit error (This context has no FetchEvent or This context has no ExecutionContext) src/context.ts:377-397.
Sources: src/context.ts:377-397
Related
On This Page
OverviewContext Lifecycle and Internal StateConstructor Initialization and Request WrappingExecution State and Response TrackingRequest Parameter and Header AccessorsHonoRequest Initialization and Parameter RetrievalVariable Storage and Context TypingContext Variable Map and Generic ConstraintsResponse Construction and HelpersResponse Instance Creation and Default TypesStream SSE Execution WalkthroughAdapter Bindings and Environment AccessRuntime Identification and Environment ResolutionPlatform Adapter Handlers and Execution ContextsRelated