Skip to main content
doc0blade47/honodocumentation home
Sign in

Execution Context

Last updated Oct 12, 2026100% grounded
Relevant source files

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

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 / PropertyInternal VariableTypeDescription
req#req, #rawRequestHonoRequest | undefined, RequestLazily wrapped request accessor and underlying fetch request src/context.ts:300-301, 366-369
envenvE['Bindings']Environment variables, secrets, and bindings object src/context.ts:315
errorerrorError | undefinedCaptured error object thrown during middleware or handler execution src/context.ts:333
event#executionCtxFetchEventLikeFetchEvent instance supporting respondWith src/context.ts:336, 377-383
executionCtx#executionCtxExecutionContextPlatform execution context supporting methods like waitUntil src/context.ts:336, 391-397
res#res, #preparedHeadersResponse, Headers | undefinedCurrent response object and prepared headers buffer src/context.ts:337, 341, 403-407
finalizedfinalizedbooleanFlag 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> | undefined

Sources: 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

  1. streamSSE initializes a TransformStream and wraps it in an SSEStreamingApi instance src/helper/streaming/sse.ts:77-78.
  2. isOldBunVersion() checks for legacy runtime behavior, binding an abort listener to c.req.raw.signal if necessary src/helper/streaming/sse.ts:80-87.
  3. contextStash.set caches the context against the response readable stream to prevent premature garbage collection in Bun src/helper/streaming/sse.ts:89-90.
  4. c.header sets required transport and protocol headers including Transfer-Encoding, Content-Type, Cache-Control, and Connection src/helper/streaming/sse.ts:92-95, src/context.ts:515-527.
  5. run executes the user callback with error handling and automatic stream closure src/helper/streaming/sse.ts:47-68, 97.
  6. createResponseInstance is invoked through response helpers to construct the final Response wrapping stream.responseReadable src/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 KeyDetection MechanismEnvironment Source
bunnavigator.userAgent starts with Bunprocess.env
nodenavigator.userAgent starts with Node.js or process.release.name === 'node'process.env
denonavigator.userAgent starts with DenoDeno.env.toObject()
workerdnavigator.userAgent starts with Cloudflare-Workersc.env
edge-lightglobalThis.EdgeRuntime is a stringprocess.env
fastlyglobalThis.fastly !== undefined{}
otherFallback 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

Was this page helpful?