Error Handling
Centralize error handling for your router with the onError configuration, and distinguish internal router errors from your own application errors.
Aura Router provides a robust, phase-aware error handling system. The recommended approach is to use Lifecycle Hooks (hooks.onError), which provide granular control over failures at both the endpoint and router levels.
Deprecation Notice: The legacy top-level onError configuration is still supported for backward compatibility but is
deprecated. We strongly recommend migrating to hooks.onError.
hooks.onError
The onError hook is the only lifecycle hook that runs conditionally—it executes exclusively when an error is thrown during any earlier phase of the request pipeline. It is your centralized location to intercept failures, log diagnostics, and shape the HTTP response sent to the client.
The context object passed to onError contains:
phase: The exact lifecycle phase where the crash occurred (onRequest,onHeaders,onParams,onBody,onHandler, etc.).error: The raw error object that was thrown.json/context: Standard context helpers to construct responses or access injected dependencies.
A router-level onError hook captures errors arising from any endpoint, middleware, or internal router logic. It is the ideal place to format standard 500 Internal Server Error responses.
import { createRouter, createEndpoint } from "@aura-stack/router"
const getBook = createEndpoint("GET", "/books/:bookId", (ctx) => {
throw new Error("Database connection lost")
})
export const router = createRouter([getBook], {
hooks: {
onError: ({ phase, error, json, context }) => {
// 💡 `context.log` is a custom injected service
context.log.error(`[${phase}] Crash: ${error.message}`)
return json({ error: `Internal execution failed during ${phase}` }, { status: 500 })
},
},
})An endpoint-level onError hook handles errors only for that specific route.
import { createEndpoint } from "@aura-stack/router"
export const getBook = createEndpoint(
"GET",
"/books/:bookId",
(ctx) => {
return ctx.json({ bookId: ctx.params.bookId })
},
{
hooks: {
onHeaders: () => {
throw new Error("Missing required trace headers")
},
onError: ({ phase, error, json }) => {
// Handle failures specific to this endpoint's strict requirements
return json({ message: `Book API Error (${phase}): ${error.message}` }, { status: 400 })
},
},
}
)Identifying Router Errors
To detect whether an error originated from Aura Router's internal mechanics (like a malformed URL or a missing route) versus your own application logic, use the isRouterError helper.
A RouterError provides:
message: A descriptive string of the failure.statusCode: The appropriate HTTP status code (e.g.,404,400,500).statusText: The HTTP status text (e.g.,Not Found,Bad Request).
import { createRouter, isRouterError } from "@aura-stack/router"
export const router = createRouter([], {
hooks: {
onError: ({ error, json }) => {
// 1. Handle Aura Router internal errors safely
if (isRouterError(error)) {
return json({ error: error.statusText, details: error.message }, { status: error.statusCode })
}
// 2. Handle your custom application errors
if (error instanceof DOMException && error.name === "TimeoutError") {
return json({ error: "Upstream service timeout" }, { status: 504 })
}
// 3. Uncaught exceptions (Do not leak stack traces to the client!)
console.error("[Unhandled Exception]", error)
return json({ error: "Internal Server Error" }, { status: 500 })
},
},
})Legacy Config onError
Prior to the introduction of lifecycle hooks, Aura Router relied on a top-level onError configuration property. While still supported, this API lacks access to the rich ctx object and phase awareness.
Unlike hooks, this legacy callback receives only the raw error and standard request objects. You must construct the response
using the native Response.json() API rather than ctx.json().
import { createRouter, isRouterError, type RouterConfig } from "@aura-stack/router"
const onErrorHandler: RouterConfig["onError"] = (error) => {
if (isRouterError(error)) {
return Response.json({ message: "Unexpected Internal Error" }, { status: error.statusCode })
}
console.error(error)
return Response.json({ message: "Internal Server Error" }, { status: 500 })
}Unlike endpoint hooks, this callback doesn't receive a ctx object — only the raw error and the original request — so
responses here are built with Response.json, not ctx.json.
import { createRouter, isRouterError, type RouterConfig } from "@aura-stack/router"
const onError: RouterConfig["onError"] = (error, request) => {
if (isRouterError(error)) {
const { message, statusText, statusCode } = error
return Response.json(
{
error: statusText,
error_description: message,
},
{ status: statusCode }
)
}
return Response.json({ message: "Internal Server Error" }, { status: 500 })
}
export const router = createRouter([], {
onError,
})