setup()
Creates an application with lifecycle callbacks and routes.
import { setup, route } from "@hectoday/http";
const app = setup({
onRequest: ({ request }) => {
/* ... */
},
onResponse: ({ request, response, locals }) => {
/* ... */
return response;
},
onError: ({ error, request, locals }) => {
/* ... */
},
onNotFound: ({ request, locals }) => {
/* ... */
},
routes: [
/* ... */
],
}); Return value
setup() returns an object with fetch, request, and routes:
const app = setup({
routes: [
/* ... */
],
});
// Use with srvx
serve({ fetch: app.fetch, port: 3000 });
// Use in tests
const res = await app.request("/health"); app.fetch has the signature (request: Request) => Response | Promise<Response>. It works with any server that accepts this signature: srvx, Bun, Deno, Cloudflare Workers.
app.request is a convenience method for testing. It takes a path and optional options, builds a Request internally, and returns a Promise<Response>.
interface RequestOptions {
method?: string;
body?: unknown;
headers?: Record<string, string>;
query?: Record<string, string | string[]>;
} app.routes is the array of route descriptors, useful for passing to openapi().
Configuration options
routes
Type: RouteDescriptor[] — Required
Array of route definitions created with route.get(), route.post(), etc.
setup({
routes: [
route.get("/books", { resolve: () => Response.json([]) }),
route.post("/books", {
resolve: (c) => {
/* ... */
},
}),
],
}); onRequest
Type: (args: { request: Request }) => TLocals | Promise<TLocals> | void | Promise<void>
Runs before every route handler. Use it for request IDs, timing, or any per-request setup.
Returns an object that becomes c.locals in route handlers. If it returns void or undefined, locals is an empty object.
onRequest: ({ request }) => {
return { requestId: crypto.randomUUID(), startTime: Date.now() };
}, To reject a request early, throw a Response. Thrown Response objects bypass onError and onResponse and are returned directly to the client.
onRequest: ({ request }) => {
const key = request.headers.get("x-api-key");
if (key !== "valid-key") {
throw Response.json(
{ error: "Invalid API key" },
{ status: 401 },
);
}
return {};
}, onResponse
Type: (args: { request: Request; response: Response; locals: TLocals }) => Response | Promise<Response>
Runs after every route handler. Use it for logging, adding headers, or collecting metrics. Must return a Response.
onResponse: ({ request, response, locals }) => {
const duration = Date.now() - locals.startTime;
response.headers.set("X-Request-Id", locals.requestId);
response.headers.set("X-Response-Time", `${duration}ms`);
return response;
}, You can modify the response headers and return the same response, or return an entirely new Response.
onError
Type: (args: { error: Error; request: Request; locals: Partial<TLocals> }) => Response | Promise<Response>
Runs when a route handler or onRequest throws an Error. Must return a Response.
locals is Partial<TLocals> because onRequest may not have completed before the error occurred.
onError: ({ error, request, locals }) => {
console.error("Unhandled error:", error.message);
return Response.json(
{ error: { code: "INTERNAL_ERROR", message: "An unexpected error occurred" } },
{ status: 500 },
);
}, Thrown Response objects bypass onError. Only thrown Error objects reach onError.
onNotFound
Type: (args: { request: Request; locals: TLocals }) => Response | Promise<Response>
Runs when no route matches the request. Returns a custom 404 response.
onNotFound: ({ request, locals }) => {
const url = new URL(request.url);
return Response.json(
{ error: { code: "NOT_FOUND", message: `No route for ${request.method} ${url.pathname}` } },
{ status: 404 },
);
}, If not provided, the framework returns a default 404 response.
Lifecycle order
Request arrives
|
v
onRequest({ request })
| returns locals (or throws)
v
Route handler ({ request, input, locals })
| returns Response (or throws)
v
onResponse({ request, response, locals })
| returns Response
v
Response sent to client If onRequest or the route handler throws an Error:
throw Error
|
v
onError({ error, request, locals })
| returns Response
v
onResponse({ request, response, locals })
|
v
Response sent to client If onRequest or the route handler throws a Response:
throw Response
|
v
Response sent to client (bypasses onError AND onResponse)