Skip to content

Error Handling ​

Hedystia provides multiple mechanisms to handle errors — both expected errors (validation failures, business logic errors) and unexpected ones (runtime exceptions).

Throwing Errors from a Handler ​

Every handler context includes an error helper that throws a typed HTTP error:

ts
import 
Hedystia
, {
h
} from 'hedystia'
const
app
= new
Hedystia
()
.
get
('/error', ({
error
}) => {
return
error
(418, "I'm a teapot")
})

Calling error() throws internally. The framework catches it and serializes it as:

Response
{
  "message": "User not found",
  "code": 404
}

Typed Error Responses ​

Declare the shape of your error response with the error schema field. This drives client-side type inference.

ts
app
.
get
(
'/users/:id', ({
params
,
error
}) => {
if (
params
.
id
=== 0)
error
(404, 'User not found')
return {
id
:
params
.
id
}
}, {
params
:
h
.
object
({
id
:
h
.
number
().
coerce
() }),
response
:
h
.
object
({
id
:
h
.
number
() }),
error
:
h
.
object
({
message
:
h
.
string
(),
code
:
h
.
number
(),
}), } )

On the client side:

ts
import { 
createClient
} from '@hedystia/client'
import type {
app
} from './server'
const
client
=
createClient
<typeof
app
>('http://localhost:3000')
const {
data
,
error
} = await
client
.
users
.
id
(0).
get
()
if (
error
) {
console
.
log
(
error
.
message
) // typed as string
console
.
log
(
error
.
code
) // typed as number
}

Static Error Constructor ​

You can also use the static Hedystia.error() method from outside a handler context:

typescript
import Hedystia from 'hedystia'

function requireAuth(token?: string) {
  if (!token) {
    throw { statusCode: 401, message: 'Missing authorization token' }
  }
}

Global Error Handler ​

Use onError to catch all uncaught errors and return a custom response:

ts
const 
app
= new
Hedystia
()
.
onError
((
error
,
ctx
) => {
if (
error
.statusCode === 401) {
return {
error
: 'You shall not pass' }
} const
status
=
error
.statusCode ?? 500
const
message
=
error
.
message
?? 'Something went wrong'
ctx
.
set
.
status
(
status
)
return {
error
:
message
,
status
}
}) .
get
('/crash', () => {
throw new
Error
('Unexpected failure')
}) .
listen
(3000)

Security Errors ​

When security controls are enabled, the framework may produce the following error responses before the handler runs:

ScenarioStatus
Request body exceeds bodyLimit413
Dangerous input key detected400
Input exceeds maxStringLength400
Input exceeds maxDepth400
Too many query parameters400
Rate limit exceeded429
Handler or middleware timed out408

These errors are instances of SecurityInputError, RateLimitError, or RequestTimeoutError and can be caught by onError.

Validation Errors ​

When request data fails schema validation, Hedystia automatically responds with a 400 error before invoking your handler:

ScenarioStatus
Invalid path params400
Invalid query params400
Invalid request body400
Invalid headers400

You do not need to handle validation errors manually.

Subscription Errors ​

In subscriptions, use sendError to push a typed error to the client without closing the connection:

ts
app
.
subscription
(
'/notifications/:userId', async ({
params
,
sendData
,
sendError
}) => {
if (
params
.
userId
=== 'banned') {
sendError
({
reason
: 'Account suspended',
severity
: 'high' })
return }
sendData
({
message
: 'Welcome back!',
timestamp
:
Date
.
now
() })
}, {
params
:
h
.
object
({
userId
:
h
.
string
() }),
data
:
h
.
object
({
message
:
h
.
string
(),
timestamp
:
h
.
number
(),
}),
error
:
h
.
object
({
reason
:
h
.
string
(),
severity
:
h
.
options
(
h
.
literal
('low'),
h
.
literal
('high')),
}), } )

CORS and Preflight ​

CORS errors are handled automatically when CORS is enabled:

typescript
const app = new Hedystia({ cors: true })

Or with fine-grained control:

typescript
const app = new Hedystia({
  cors: {
    origin: ['https://app.example.com'],
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
  },
})

Preflight (OPTIONS) requests are handled automatically — no extra routes needed.