Errors & notFound
Distinguish validation 400s, custom 404s, thrown errors, and how the typed client surfaces them.
Three paths
- Schema validation - 400 JSON with
details, before the handler; not passed toonError - No matching route -
notFoundor default 404 JSON - Thrown errors - middleware/handler throws →
onErroror default 500
Server
import { HttpServer, z } from "jsr:@webtools/expressapi";
export const server = new HttpServer()
.get("/users/:id", (req, res) => {
if (req.params.id === "boom") throw new Error("Simulated failure");
return res.json({ id: req.params.id });
}, [], {
params: z.object({ id: z.string().min(1) }),
})
.notFound((req, res) =>
res.status(404).json({
success: false,
error: "Not Found",
path: req.url,
method: req.method,
}))
.onError((error, req, res) =>
res.status(500).json({
success: false,
error: "Internal Server Error",
message: error instanceof Error ? error.message : String(error),
path: req.url,
}));
export type AppRouter = typeof server;Client
import type { AppRouter } from "./server.ts";
import { HttpClient, HttpClientError } from "jsr:@webtools/expressapi";
const client = new HttpClient<AppRouter>({ baseUrl: "http://localhost:5050" });
try {
await client.get("/users/:id", { params: { id: "boom" } });
} catch (error) {
if (error instanceof HttpClientError) {
// 500 body from onError
console.log(error.status, error.body);
}
}
try {
await client.get("/missing" as "/users/:id", { params: { id: "x" } });
} catch (error) {
if (error instanceof HttpClientError) {
// 404 body from notFound (if the URL were valid at the type level)
}
}