Skip to content

Route Handlers

A route handler is a function that receives a context object and returns a response. In Hedystia, handlers are always fully typed based on the schema you provide.

Handler Signature

typescript
(ctx: Context) => Response | any | Promise<Response | any>

The Context type is inferred from the route schema.

Basic Handlers

Returning plain objects

The most common pattern — Hedystia serializes it as JSON:

ts
app
.
get
('/users', () => {
return [{
id
: 1,
name
: 'Alice' }, {
id
: 2,
name
: 'Bob' }]
})

Returning strings

Returned as text/plain:

ts
import 
Hedystia
from 'hedystia'
const
app
= new
Hedystia
()
app
.
get
('/ping', () => 'pong')

Returning a full Response

For complete control:

ts
import 
Hedystia
from 'hedystia'
import {
Bun
} from 'bun'
const
app
= new
Hedystia
()
app
.
get
('/download', () => {
const
file
=
Bun
.file('./data.csv')
return new
Response
(
file
, {
headers
: {
'Content-Type': 'text/csv', 'Content-Disposition': 'attachment; filename="data.csv"', }, }) })

Accessing Context

ts
app
.
post
(
'/orders', async ({
body
,
params
,
query
,
headers
,
set
,
error
}) => {
// Body is typed from body schema const
order
= await
createOrder
(
body
)
// Set a custom response header
set
.
headers
.
set
('x-order-id',
String
(
order
.id))
return
order
}, {
body
:
h
.
object
({
product
:
h
.
string
(),
quantity
:
h
.
number
().
min
(1),
}), } )

Async Handlers

Handlers can be async — errors thrown inside will be caught by onError:

ts
import 
Hedystia
from 'hedystia'
const
fetchFromDatabase
= async () => ({})
const
app
= new
Hedystia
()
app
.
get
('/data', async ({
error
}) => {
try { const
result
= await
fetchFromDatabase
()
return
result
} catch (
e
) {
error
(500, 'Database error')
} })

Handler Patterns

Conditional Responses

ts
app
.
get
(
'/users/:id', async ({
params
,
error
}) => {
const
user
= await
db
.
findUser
(
params
.
id
)
if (!
user
)
error
(404, 'User not found')
return
user
}, {
params
:
h
.
object
({
id
:
h
.
number
().
coerce
() }),
response
:
h
.
object
({
id
:
h
.
number
(),
name
:
h
.
string
() }),
error
:
h
.
object
({
message
:
h
.
string
(),
code
:
h
.
number
() }),
} )

Setting Status Codes

ts
import 
Hedystia
, {
h
} from 'hedystia'
const
createUser
= (
body
: any) => ({
id
: 1, ...
body
})
const
app
= new
Hedystia
()
app
.
post
(
'/users', ({
body
,
set
}) => {
const
user
=
createUser
(
body
)
set
.
status
(201) // Created
return
user
}, {
body
:
h
.
object
({
name
:
h
.
string
() }),
} )

Setting Cookies

ts
import 
Hedystia
from 'hedystia'
const
authenticate
= (
u
: any,
p
: any) => 'token'
const
app
= new
Hedystia
()
app
.
post
('/login', ({
body
,
set
}) => {
const
token
=
authenticate
((
body
as any).username, (
body
as any).password)
set
.
cookies
.
set
('session',
token
, {
httpOnly
: true,
secure
: true,
sameSite
: 'lax',
maxAge
: 60 * 60 * 24 * 7, // 1 week
}) return {
ok
: true }
})

Redirects

ts
import 
Hedystia
from 'hedystia'
const
app
= new
Hedystia
()
app
.
get
('/old-path', ({
set
}) => {
set
.
status
(301)
set
.
headers
.
set
('Location', '/new-path')
return null })

Handler with Macros

When a macro is enabled, its resolved value is available on the context:

ts
const 
app
= new
Hedystia
()
.
macro
({
auth
: () => ({
resolve
: async (
ctx
) => {
const
token
=
ctx
.
req
.
headers
.
get
('authorization')?.
slice
(7)
if (!
token
)
ctx
.
error
(401, 'Missing token')
return {
userId
: 1,
roles
: ['admin'] }
}, }), }) .
get
(
'/dashboard', async (
ctx
) => {
const {
userId
,
roles
} = await
ctx
.
auth
return {
userId
,
roles
,
data
: 'private data' }
}, {
auth
: true,
response
:
h
.
object
({
userId
:
h
.
number
(),
roles
:
h
.
string
().
array
(),
data
:
h
.
string
(),
}), } )

WebSocket Handlers

Define WebSocket behavior alongside HTTP routes:

typescript
app.ws('/chat', {
  open(ws) {
    console.log('Client connected from', ws.remoteAddress)
    ws.send(JSON.stringify({ type: 'welcome' }))
  },
  message(ws, msg) {
    const parsed = JSON.parse(msg.toString())
    ws.publish('chat', JSON.stringify({ from: 'server', data: parsed }))
  },
  close(ws, code, reason) {
    console.log(`Disconnected: ${code} ${reason}`)
  },
  error(ws, error) {
    console.error('WebSocket error:', error)
  },
})