Skip to content

Context Object

The Context object wraps the native Request and provides convenient methods for accessing request data, setting response headers, and sending responses.

What is Context?

Context is a wrapper around Deno's native Request object. Every incoming request is wrapped in one Context that flows from middleware to route handler. Instead of working with raw Request directly, you use Context which gives you:

  • Lazy parsing - Data is parsed only when you access it
  • Convenient methods - Simple APIs for common operations
  • Response utilities - Built-in methods for sending responses
  • Header management - Easy response header manipulation

Why Using Context?

Context avoids multiple parsing and repeated processing during the request lifecycle. The handler receives one Context object that persists through the entire lifecycle — from middleware to route handler.

Creating Context

Deserve creates Context automatically when requests arrive:

typescript
// 1. Import Context type
import type { Context } from '@neabyte/deserve'

// 2. Handler receives ctx (Deserve creates it per request)
export function GET(ctx: Context): Response {
  return ctx.send.json({ message: 'Hello' })
}

Context Structure

Context wraps several key pieces:

  1. Original Request - Access via ctx.request
  2. Parsed URL - Used internally for query params
  3. Route Parameters - Extracted from dynamic routes
  4. Response Headers - Set before sending response

Lazy Parsing

Context uses lazy parsing for performance: data (query, body, cookie, header) is parsed only when you call the method that uses it, then the result is cached for later calls.

typescript
export function GET(ctx: Context): Response {
  // 1. Query not parsed until ctx.query() is called
  const query = ctx.query()
  // 2. Result cached; next call uses cache

  // 3. Body parsed on first access (based on Content-Type)
  const body = await ctx.body()

  // 4. Send combined query + body
  return ctx.send.json({ query, body })
}

Request Data Access

Access request data through Context methods:

  • Query Parameters - ctx.query(), ctx.queries()
  • Route Parameters - ctx.param(), ctx.params()
  • Headers - ctx.header(), ctx.headers
  • Cookies - ctx.cookie()
  • Body - ctx.body(), ctx.json(), ctx.formData(), ctx.text(), ctx.arrayBuffer(), ctx.blob()
  • URL Information - ctx.url, ctx.pathname

Response Utilities

Send responses using ctx.send:

  • ctx.send.json() - JSON response
  • ctx.send.text() - Plain text
  • ctx.send.html() - HTML content
  • ctx.send.file() - File download
  • ctx.send.data() - In-memory data download
  • ctx.send.stream() - Stream response (ReadableStream)
  • ctx.send.redirect() - Redirect
  • ctx.send.custom() - Custom response
  • ctx.handleError() - Error handling

You can also use ctx.redirect() directly as a convenience method:

typescript
export function GET(ctx: Context): Response {
  // 1. Redirect 301 to new path (shorthand for ctx.send.redirect)
  return ctx.redirect('/new-location', 301)
}

Response Headers

Set response headers before sending:

typescript
export function GET(ctx: Context): Response {
  ctx.setHeader('X-Custom', 'value')
  ctx.setHeader('Cache-Control', 'no-cache')
  return ctx.send.json({ data: 'test' })
}

Setting Multiple Headers

Use setHeaders() to set multiple headers at once:

typescript
export function GET(ctx: Context): Response {
  ctx.setHeaders({
    'X-Custom': 'value',
    'Cache-Control': 'no-cache',
    'X-Request-ID': 'abc123'
  })
  return ctx.send.json({ data: 'test' })
}

Reading Response Headers

Access all response headers that have been set:

typescript
export function GET(ctx: Context): Response {
  ctx.setHeader('X-Custom', 'value')
  ctx.setHeader('Cache-Control', 'no-cache')
  const headers = ctx.responseHeadersMap // { 'X-Custom': 'value', 'Cache-Control': 'no-cache' }
  return ctx.send.json({ data: 'test' })
}

URL and Pathname

Get URL information directly:

  • ctx.url - Full URL string
  • ctx.pathname - Pathname portion of URL (e.g., /api/users/123)
typescript
export function GET(ctx: Context): Response {
  const fullUrl = ctx.url // 'http://localhost:8000/api/users/123?sort=name'
  const path = ctx.pathname // '/api/users/123'
  return ctx.send.json({ path, fullUrl })
}

Error Handling

Handle errors consistently using ctx.handleError():

typescript
export function GET(ctx: Context): Response {
  try {
    if (!isAuthorized) {
      return ctx.handleError(401, new Error('Unauthorized'))
    }
    return ctx.send.json({ data: 'success' })
  } catch (error) {
    return ctx.handleError(500, error as Error)
  }
}

How It Works

ctx.handleError() respects your global error handler set with router.catch():

  • If router.catch() is defined - Uses your custom error handler
  • If no error handler - Returns a simple response with the status code

Use in Middleware

Middleware can use ctx.handleError() to trigger error handling:

typescript
router.use(async (ctx, next) => {
  if (!isValid) {
    return ctx.handleError(401, new Error('Unauthorized'))
    // This will use router.catch() if defined
  }
  return await next()
})

Context Lifecycle

  1. Request arrives - Deserve creates Context with Request and URL
  2. Route matching - Route parameters extracted and added to Context
  3. Middleware execution - Context passed through middleware chain
  4. Route handler - Your handler receives Context
  5. Response sent - Context methods used to build Response

Released under the MIT License.