Skip to main content
The context object is passed to every route handler and lifecycle hook in Elysia. It contains all the information and utilities needed to handle a request.

Overview

The context object provides access to:
  • Request data (body, params, query, headers)
  • Response configuration (status, headers, cookies)
  • Server utilities (redirect, status helpers)
  • Custom decorators and state
import { Elysia } from 'elysia'

const app = new Elysia()
  .get('/example', (context) => {
    // Access all context properties
    const { body, params, query, headers, set, request } = context
    
    return { message: 'Success' }
  })

Request properties

body

The parsed request body:
app.post('/users', ({ body }) => {
  // body is automatically parsed based on Content-Type
  return { created: body }
})

// With validation
import { t } from 'elysia'

app.post('/users', ({ body }) => {
  const { name, email } = body
  return { id: 1, name, email }
}, {
  body: t.Object({
    name: t.String(),
    email: t.String()
  })
})
The body is parsed automatically based on the Content-Type header. Supported types include JSON, form data, and text.

params

URL path parameters:
app.get('/users/:id', ({ params }) => {
  return { userId: params.id }
})

app.get('/posts/:postId/comments/:commentId', ({ params }) => {
  const { postId, commentId } = params
  return { postId, commentId }
})

query

Query string parameters:
app.get('/search', ({ query }) => {
  const { q, limit = '10', offset = '0' } = query
  return { query: q, limit, offset }
})
// GET /search?q=elysia&limit=20

headers

Request headers:
app.get('/api/data', ({ headers }) => {
  const contentType = headers['content-type']
  const auth = headers['authorization']
  
  return { contentType, auth }
})
Request cookies:
app.get('/profile', ({ cookie }) => {
  const sessionId = cookie.sessionId?.value
  
  return { sessionId }
})

request

The raw Request object:
app.get('/info', ({ request }) => {
  return {
    url: request.url,
    method: request.method,
    headers: Object.fromEntries(request.headers)
  }
})

path

The actual URL path from the request:
app.get('/users/:id', ({ path }) => {
  return { path } // "/users/123"
})

route

The registered route pattern:
app.get('/users/:id', ({ route }) => {
  return { route } // "/users/:id"
})

Response configuration

set

Configure the response:
app.get('/example', ({ set }) => {
  // Set status code
  set.status = 201
  
  // Set headers
  set.headers['Cache-Control'] = 'max-age=3600'
  set.headers['X-Custom-Header'] = 'value'
  
  return { message: 'Created' }
})

set.status

Set the HTTP status code:
app.post('/users', ({ set }) => {
  set.status = 201
  return { created: true }
})

app.get('/error', ({ set }) => {
  set.status = 404
  return { error: 'Not found' }
})

set.headers

Set response headers:
app.get('/api/data', ({ set }) => {
  set.headers['Content-Type'] = 'application/json'
  set.headers['Cache-Control'] = 'no-cache'
  set.headers['X-API-Version'] = '1.0'
  
  return { data: 'value' }
})

set.redirect

This property is deprecated. Use the redirect function instead.
// Don't use
app.get('/old', ({ set }) => {
  set.redirect = '/new'
})

// Use instead
app.get('/old', ({ redirect }) => {
  return redirect('/new')
})

status

Type-safe status code setter:
app.get('/users/:id', async ({ params, status }) => {
  const user = await findUser(params.id)
  
  if (!user) {
    return status(404, { error: 'User not found' })
  }
  
  return status(200, user)
})

app.post('/users', ({ body, status }) => {
  const user = createUser(body)
  return status(201, user)
})

redirect

Redirect to another URL:
app.get('/old-path', ({ redirect }) => {
  return redirect('/new-path')
})

app.get('/external', ({ redirect }) => {
  return redirect('https://example.com')
})

// With status code (default is 302)
app.get('/permanent', ({ redirect }) => {
  return redirect('/new-location', 301)
})

Server properties

server

Access to the underlying server instance:
app.get('/info', ({ server }) => {
  return {
    port: server?.port,
    hostname: server?.hostname
  }
})
The server property is null until .listen() is called.

Custom properties

store

Global application state:
const app = new Elysia()
  .state('version', '1.0.0')
  .state('db', database)
  .get('/info', ({ store }) => {
    return { version: store.version }
  })
  .post('/users', async ({ body, store }) => {
    const user = await store.db.users.create(body)
    return user
  })

decorator

Custom utilities and helpers:
const app = new Elysia()
  .decorate('getDate', () => new Date())
  .decorate('log', console.log)
  .get('/time', ({ getDate }) => {
    return { time: getDate() }
  })

derive

Derived properties from the request:
const app = new Elysia()
  .derive(({ headers }) => {
    return {
      auth: headers['authorization']?.split(' ')[1]
    }
  })
  .get('/protected', ({ auth }) => {
    if (!auth) return { error: 'Unauthorized' }
    return { auth }
  })

resolve

Lazy-computed properties:
const app = new Elysia()
  .resolve(({ headers }) => {
    return {
      user: async () => {
        const token = headers['authorization']
        return await getUserFromToken(token)
      }
    }
  })
  .get('/profile', async ({ user }) => {
    return await user()
  })

Type safety

Elysia provides full type inference for the context:
import { Elysia, t } from 'elysia'

const app = new Elysia()
  .state('counter', 0)
  .decorate('increment', (n: number) => n + 1)
  .get('/count', ({ store, increment }) => {
    // store.counter is typed as number
    // increment is typed as (n: number) => number
    
    store.counter = increment(store.counter)
    return { count: store.counter }
  })

Destructuring

Commonly, you’ll destructure just the properties you need:
app.get('/users/:id', async ({ params, store, status }) => {
  const user = await store.db.users.findById(params.id)
  
  if (!user) {
    return status(404, { error: 'Not found' })
  }
  
  return user
})

Best practices

Only destructure the context properties you actually use. This makes your code cleaner and easier to understand.
// Good
app.get('/users', ({ store }) => store.users)

// Avoid
app.get('/users', (context) => context.store.users)
Always validate request data with schemas to ensure type safety:
app.post('/users', ({ body }) => body, {
  body: t.Object({
    name: t.String(),
    email: t.String()
  })
})
Use derive for synchronous derived values and resolve for async operations.

Next steps

Lifecycle

Learn about lifecycle hooks

Validation

Validate request data with schemas

Build docs developers (and LLMs) love