Skip to main content
Request handlers are functions that process incoming HTTP requests and return responses. In Elysia, handlers receive a context object with everything needed to handle the request.

Basic handlers

A handler can return any value, which Elysia automatically converts to an appropriate response:
import { Elysia } from 'elysia'

const app = new Elysia()
  // Return string
  .get('/', () => 'Hello World')
  
  // Return number
  .get('/number', () => 617)
  
  // Return boolean
  .get('/bool', () => true)
  
  // Return JSON object
  .get('/json', () => ({ name: 'Elysia' }))
  
  // Return array
  .get('/array', () => [1, 2, 3])
  .listen(3000)

Handler parameters

Handlers receive a context object with request information:
app.get('/user/:id', ({ params, query, body, headers }) => {
  return {
    id: params.id,
    query: query,
    contentType: headers['content-type']
  }
})
See the Context documentation for details on all available properties.

Async handlers

Handlers can be asynchronous:
app.get('/users', async () => {
  const users = await database.users.findMany()
  return users
})

app.post('/users', async ({ body }) => {
  const user = await database.users.create(body)
  return user
})
Elysia automatically handles promises and awaits async handlers.

Setting response status

Use the set object to configure the response:
app.post('/users', ({ body, set }) => {
  set.status = 201 // Created
  return { id: 1, ...body }
})

app.get('/not-found', ({ set }) => {
  set.status = 404
  return { error: 'Not found' }
})
Or use the status helper for type-safe status codes:
app.get('/error', ({ status }) => {
  return status(500, { error: 'Internal server error' })
})

app.post('/created', ({ status, body }) => {
  return status(201, { id: 1, ...body })
})

Setting headers

Add custom headers to the response:
app.get('/api/data', ({ set }) => {
  set.headers['Cache-Control'] = 'max-age=3600'
  set.headers['X-Custom-Header'] = 'value'
  return { data: 'cached data' }
})
Set default headers for all routes:
const app = new Elysia()
  .headers({
    'Access-Control-Allow-Origin': '*',
    'X-Powered-By': 'Elysia'
  })
  .get('/', () => 'Hello')

Redirects

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

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

// With status code
app.get('/moved', ({ redirect }) => {
  return redirect('/new-location', 301) // Permanent redirect
})

Response types

JSON responses

Return objects or arrays for automatic JSON serialization:
app.get('/users', () => [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
])

Text responses

Return strings for plain text:
app.get('/text', () => 'Plain text response')

HTML responses

Set the content type for HTML:
app.get('/page', ({ set }) => {
  set.headers['Content-Type'] = 'text/html'
  return '<h1>Hello World</h1>'
})

Custom Response objects

Return a Response object for full control:
app.get('/custom', () => {
  return new Response('Custom response', {
    status: 418,
    headers: {
      'Content-Type': 'text/plain',
      'X-Custom': 'header'
    }
  })
})

Streaming responses

Stream data to the client:
app.get('/stream', () => {
  const stream = new ReadableStream({
    start(controller) {
      controller.enqueue('chunk 1\n')
      controller.enqueue('chunk 2\n')
      controller.close()
    }
  })
  
  return new Response(stream)
})

Inline route configuration

Configure validation and other options inline:
import { Elysia, t } from 'elysia'

app.post('/users', ({ body }) => {
  return { id: 1, ...body }
}, {
  body: t.Object({
    name: t.String(),
    email: t.String()
  })
})

Handler composition

const getUsers = async () => {
  return await database.users.findMany()
}

const getUserById = async ({ params }: { params: { id: string } }) => {
  return await database.users.findById(params.id)
}

app
  .get('/users', getUsers)
  .get('/users/:id', getUserById)

Error handling

Throw errors to trigger error handlers:
app
  .get('/user/:id', async ({ params }) => {
    const user = await database.users.findById(params.id)
    
    if (!user) {
      throw new Error('User not found')
    }
    
    return user
  })
  .onError(({ code, error, set }) => {
    if (code === 'NOT_FOUND') {
      set.status = 404
      return { error: 'Not found' }
    }
    
    set.status = 500
    return { error: error.message }
  })
Always handle errors appropriately to prevent exposing sensitive information.

Best practices

Each handler should do one thing well. Extract complex logic to separate functions.
Always use async/await for database queries, file operations, and external API calls.
Use correct HTTP status codes:
  • 200 for success
  • 201 for created resources
  • 204 for no content
  • 400 for bad requests
  • 401 for unauthorized
  • 404 for not found
  • 500 for server errors
Use schema validation to catch invalid input before processing.

Next steps

Context

Learn about the context object

Validation

Validate request data with schemas

Build docs developers (and LLMs) love