Overview
The Context object (c) is passed to every handler and middleware function. It provides access to the request, response, environment variables, and utility methods for building responses.
From src/context.ts:293-299:
export class Context <
E extends Env = any ,
P extends string = any ,
I extends Input = {},
> {
// Context implementation
}
Request Access
Access the request object through c.req:
app . get ( '/users/:id' , ( c ) => {
// Get path parameters
const id = c . req . param ( 'id' )
const params = c . req . param () // All params
// Get query parameters
const search = c . req . query ( 'search' )
const queries = c . req . queries () // All queries
// Get headers
const auth = c . req . header ( 'Authorization' )
const headers = c . req . header () // All headers
// Get request method and path
const method = c . req . method
const path = c . req . path
const url = c . req . url
return c . json ({ id , search , auth , method , path })
})
From src/context.ts:364-369:
/**
* `.req` is the instance of { @link HonoRequest } .
*/
get req (): HonoRequest < P , I [ 'out' ] > {
this.#req ??= new HonoRequest (this.# rawRequest , this.# path , this.# matchResult )
return this.#req
}
Response Methods
The Context provides several methods for returning responses:
Text Response
Return plain text:
app . get ( '/hello' , ( c ) => {
return c . text ( 'Hello, World!' )
})
app . get ( '/custom' , ( c ) => {
return c . text ( 'Created' , 201 )
})
From src/context.ts:670-694:
text : TextRespond = (
text : string ,
arg ?: ContentfulStatusCode | ResponseOrInit ,
headers ?: HeaderRecord
) : ReturnType < TextRespond > => {
return ! this . #preparedHeaders && ! this . #status && ! arg && ! headers && ! this . finalized
? ( new Response ( text ) as ReturnType < TextRespond >)
: ( this . #newResponse (
text ,
arg ,
setDefaultContentType ( TEXT_PLAIN , headers )
) as ReturnType < TextRespond >)
}
JSON Response
Return JSON data:
app . get ( '/api/users' , ( c ) => {
return c . json ({
users: [
{ id: 1 , name: 'Alice' },
{ id: 2 , name: 'Bob' }
]
})
})
app . post ( '/api/users' , async ( c ) => {
const user = await c . req . json ()
return c . json ({ created: user }, 201 )
})
From src/context.ts:696-721:
json : JSONRespond = <
T extends JSONValue | {} | InvalidJSONValue ,
U extends ContentfulStatusCode = ContentfulStatusCode ,
>(
object : T ,
arg ?: U | ResponseOrInit < U >,
headers ?: HeaderRecord
) : JSONRespondReturn < T , U > => {
return this . #newResponse (
JSON . stringify ( object ),
arg ,
setDefaultContentType ( 'application/json' , headers )
) as any
}
HTML Response
Return HTML content:
app . get ( '/' , ( c ) => {
return c . html ( '<h1>Hello Hono!</h1>' )
})
app . get ( '/page' , ( c ) => {
return c . html ( `
<!DOCTYPE html>
<html>
<head><title>My Page</title></head>
<body><h1>Welcome</h1></body>
</html>
` )
})
From src/context.ts:723-733:
html : HTMLRespond = (
html : string | Promise < string >,
arg ?: ContentfulStatusCode | ResponseOrInit < ContentfulStatusCode >,
headers ?: HeaderRecord
) : Response | Promise < Response > => {
const res = ( html : string ) =>
this . #newResponse ( html , arg , setDefaultContentType ( 'text/html; charset=UTF-8' , headers ))
return typeof html === 'object'
? resolveCallback ( html , HtmlEscapedCallbackPhase . Stringify , false , {}). then ( res )
: res ( html )
}
Redirect
Redirect to another URL:
app . get ( '/old-path' , ( c ) => {
return c . redirect ( '/new-path' )
})
app . get ( '/external' , ( c ) => {
return c . redirect ( 'https://example.com' , 301 )
})
From src/context.ts:735-762:
redirect = < T extends RedirectStatusCode = 302 >(
location : string | URL ,
status ?: T
) : Response & TypedResponse < undefined , T , 'redirect' > => {
const locationString = String ( location )
this . header (
'Location' ,
! / [ ^ \x00 - \xFF ] / . test ( locationString ) ? locationString : encodeURI ( locationString )
)
return this . newResponse ( null , status ?? 302 ) as any
}
Body Response
Return raw body data:
app . get ( '/file' , ( c ) => {
const data = new Uint8Array ([ 1 , 2 , 3 ])
return c . body ( data )
})
app . get ( '/stream' , ( c ) => {
const stream = new ReadableStream ({
start ( controller ) {
controller . enqueue ( 'chunk 1' )
controller . enqueue ( 'chunk 2' )
controller . close ()
}
})
return c . body ( stream )
})
From src/context.ts:643-668:
body : BodyRespond = (
data : Data | null ,
arg ?: StatusCode | RequestInit ,
headers ?: HeaderRecord
) : ReturnType < BodyRespond > => this . #newResponse ( data , arg , headers ) as ReturnType < BodyRespond >
app . get ( '/api/data' , ( c ) => {
c . header ( 'X-Custom-Header' , 'value' )
c . header ( 'Cache-Control' , 'no-cache' )
return c . json ({ data: 'example' })
})
From src/context.ts:500-527:
header : SetHeaders = ( name , value , options ) : void => {
if ( this . finalized ) {
this . #res = createResponseInstance (( this . #res as Response ). body , this . #res )
}
const headers = this . #res ? this . #res . headers : ( this . #preparedHeaders ??= new Headers ())
if ( value === undefined ) {
headers . delete ( name )
} else if ( options ?. append ) {
headers . append ( name , value )
} else {
headers . set ( name , value )
}
}
app . get ( '/multi-header' , ( c ) => {
c . header ( 'Set-Cookie' , 'session=abc' , { append: true })
c . header ( 'Set-Cookie' , 'token=xyz' , { append: true })
return c . text ( 'Headers set' )
})
Status Code
Set HTTP status code:
app . post ( '/api/users' , async ( c ) => {
c . status ( 201 )
return c . json ({ message: 'User created' })
})
app . delete ( '/api/users/:id' , ( c ) => {
c . status ( 204 )
return c . body ( null )
})
From src/context.ts:529-531:
status = ( status : StatusCode ) : void => {
this . #status = status
}
Context Variables
Store and retrieve values during request processing:
Setting Variables
app . use ( '*' , async ( c , next ) => {
c . set ( 'requestId' , crypto . randomUUID ())
c . set ( 'startTime' , Date . now ())
await next ()
})
From src/context.ts:533-556:
set : Set <
IsAny < E > extends true
? {
Variables: ContextVariableMap & Record < string , any>
}
: E
> = ( key : string , value : unknown ) => {
this . #var ??= new Map ()
this . #var . set ( key , value )
}
Getting Variables
app . get ( '/profile' , ( c ) => {
const requestId = c . get ( 'requestId' )
const user = c . get ( 'user' )
return c . json ({ requestId , user })
})
From src/context.ts:558-580:
get : Get <
IsAny < E > extends true
? {
Variables: ContextVariableMap & Record < string , any>
}
: E
> = ( key : string ) => {
return this . #var ? this . #var . get ( key ) : undefined
}
Accessing Variables with .var
Read-only access to all variables:
app . get ( '/info' , ( c ) => {
const vars = c . var
// Access all context variables as object
return c . json ( vars )
})
From src/context.ts:582-602:
get var (): Readonly <
ContextVariableMap & ( IsAny < E [ 'Variables' ] > extends true ? Record < string , any > : E [ 'Variables' ])
> {
if (!this.# var ) {
return {} as any
}
return Object.fromEntries(this.#var)
}
Type-Safe Variables
Define variable types for type safety:
type Env = {
Variables : {
user : { id : string ; email : string }
requestId : string
}
}
const app = new Hono < Env >()
app . use ( '*' , async ( c , next ) => {
c . set ( 'requestId' , crypto . randomUUID ())
c . set ( 'user' , { id: '123' , email: '[email protected] ' })
await next ()
})
app . get ( '/profile' , ( c ) => {
const user = c . get ( 'user' ) // Type: { id: string; email: string }
const requestId = c . get ( 'requestId' ) // Type: string
return c . json ({ user , requestId })
})
Environment Variables
Access environment bindings (Cloudflare Workers, etc.):
type Env = {
Bindings : {
DB : D1Database
BUCKET : R2Bucket
API_KEY : string
}
}
const app = new Hono < Env >()
app . get ( '/data' , async ( c ) => {
const db = c . env . DB
const bucket = c . env . BUCKET
const apiKey = c . env . API_KEY
const result = await db . prepare ( 'SELECT * FROM users' ). all ()
return c . json ( result )
})
From src/context.ts:302-315:
/**
* `.env` can get bindings (environment variables, secrets, KV namespaces, D1 database, R2 bucket etc.) in Cloudflare Workers.
*
* @example
* ```ts
* // Environment object for Cloudflare Workers
* app.get('*', async c => {
* const counter = c.env.COUNTER
* })
* ```
*/
env : E [ 'Bindings' ] = {}
Execution Context
Access the execution context (for Cloudflare Workers):
app . get ( '/task' , async ( c ) => {
// Schedule background task
c . executionCtx . waitUntil (
fetch ( 'https://api.example.com/log' , {
method: 'POST' ,
body: JSON . stringify ({ request: c . req . path })
})
)
return c . text ( 'Task scheduled' )
})
From src/context.ts:385-397:
ExecutionContext Property
get executionCtx (): ExecutionContext {
if ( this . #executionCtx ) {
return this . #executionCtx as ExecutionContext
} else {
throw Error ( 'This context has no ExecutionContext' )
}
}
From src/context.ts:29-52:
export interface ExecutionContext {
/**
* Extends the lifetime of the event callback until the promise is settled.
*/
waitUntil ( promise : Promise < unknown >) : void
/**
* Allows the event to be passed through to subsequent event listeners.
*/
passThroughOnException () : void
}
Rendering
Custom Renderer
Set a custom renderer for templating:
import { html } from 'hono/html'
app . use ( '*' , async ( c , next ) => {
c . setRenderer (( content , props ) => {
return c . html ( html `
<!DOCTYPE html>
<html>
<head><title> ${ props ?. title || 'My App' } </title></head>
<body>
<header><h1> ${ props ?. title } </h1></header>
<main> ${ content } </main>
</body>
</html>
` )
})
await next ()
})
app . get ( '/' , ( c ) => {
return c . render ( 'Welcome to Hono!' , { title: 'Home' })
})
From src/context.ts:437-497:
render : Renderer = ( ... args ) => {
this . #renderer ??= ( content : string | Promise < string >) => this . html ( content )
return this . #renderer ( ... args )
}
setRenderer = ( renderer : Renderer ) : void => {
this . #renderer = renderer
}
Error Property
Access errors thrown in middleware:
app . use ( '*' , async ( c , next ) => {
await next ()
if ( c . error ) {
console . error ( 'Error occurred:' , c . error )
}
})
From src/context.ts:317-333:
/**
* `.error` can get the error object from the middleware if the Handler throws an error.
*
* @example
* ```ts
* app.use('*', async (c, next) => {
* await next()
* if (c.error) {
* // do something...
* }
* })
* ```
*/
error : Error | undefined
Response Access
Access or set the response:
app . use ( '*' , async ( c , next ) => {
await next ()
// Access response
console . log ( c . res . status )
console . log ( c . res . headers )
})
From src/context.ts:399-434:
get res (): Response {
return ( this . #res ||= createResponseInstance ( null , {
headers: ( this . #preparedHeaders ??= new Headers ()),
}))
}
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
}
Not Found Handler
Trigger the not found handler:
app . get ( '/users/:id' , async ( c ) => {
const user = await findUser ( c . req . param ( 'id' ))
if ( ! user ) {
return c . notFound ()
}
return c . json ( user )
})
From src/context.ts:764-779:
notFound = () : ReturnType < NotFoundHandler > => {
this . #notFoundHandler ??= () => createResponseInstance ()
return this . #notFoundHandler ( this )
}
Best Practices
Use TypeScript to define environment and variable types
Set context variables in middleware for cross-handler data sharing
Use appropriate response methods (json, text, html) for content types
Access c.req for all request data instead of the raw request object
Leverage c.executionCtx for background tasks in Cloudflare Workers
Don’t modify the raw request object. Use Context methods and properties for all request/response operations.