Skip to content

Error Handling

Default Behavior

Unhandled errors in route handlers and middleware produce a 500 response:

{
"error": "Something went wrong"
}

Global Error Middleware

import { createApp } from "@rune/core";
const app = createApp();
app.use(async (_ctx, next) => {
try {
await next();
} catch (err) {
const status = err instanceof HttpError ? err.status : 500;
const message = err instanceof Error ? err.message : "Internal Server Error";
return new Response(JSON.stringify({ error: message }), {
status,
headers: { "content-type": "application/json" },
});
}
});

Custom Error Classes

export class HttpError extends Error {
constructor(
public status: number,
message: string,
) {
super(message);
this.name = "HttpError";
}
}
export class NotFoundError extends HttpError {
constructor(resource = "Resource") {
super(404, `${resource} not found`);
}
}
export class BadRequestError extends HttpError {
constructor(message = "Bad request") {
super(400, message);
}
}

Usage in Controllers

@Controller("/users")
export class UserController {
@Get("/:id")
async getUser(@Param() id: string) {
const user = await db.findUser(id);
if (!user) {
throw new NotFoundError("User");
}
return user;
}
@Post("/")
createUser(@Body(CreateUserDto) dto: CreateUserDto) {
if (dto.email.endsWith("@spam.com")) {
throw new BadRequestError("Email domain not allowed");
}
return { created: true };
}
}

Validation Errors

When @Body(CreateUserDto) receives invalid data, a 400 response is returned automatically:

{
"error": "Validation failed",
"details": [
{
"property": "email",
"constraints": {
"isEmail": "email must be an email"
}
}
]
}

Async Error Wrapping

All route handlers are wrapped in try/catch internally. You can throw synchronously or return a rejected promise — both produce a 500 response (or the status from HttpError).

Per-Module Error Handling

Group related error handling behind a middleware scoped to a specific route prefix:

app.use(async (ctx, next) => {
if (!ctx.request.url.includes("/api/admin")) {
return next();
}
try {
return await next();
} catch (err) {
// Admin-specific error logging
console.error("Admin error:", err);
throw err; // Re-throw to global handler
}
});