8. Building HTTP APIs
Routing is file-based: every route.ts under src/api/routes/ becomes a URL
path, and the HTTP method exports (GET, POST, PUT, DELETE, …) become
handlers. Dynamic segments use [param] folders. Handlers receive the Hono
context.
File routes mount under a base path — /api by default — so
src/api/routes/posts/route.ts serves GET /api/posts. Change the base with
projectConfig.http.api.entryRouter.
// src/api/routes/posts/route.ts -> /api/posts
import { RouteHandler } from "@damatjs/framework/router";
export const GET: RouteHandler = async (c) => {
return c.json({ success: true, data: { posts: [] } });
};
export const POST: RouteHandler = async (c) => {
const body = await c.req.json();
return c.json({ success: true, data: body }, 201);
};
For typed params, use defineRoute<Params>:
// src/api/routes/users/[userId]/route.ts -> /api/users/:userId
import { defineRoute } from "@damatjs/framework/router";
export const GET = defineRoute<{ userId: string }>(async (c, params) => {
return c.json({ success: true, data: { id: params.userId } });
});
Validating requests
A route.ts can export a validators array — one RouteValidator per method
declaring Zod schemas for the request's body, query, and/or params. The
framework runs the matching validator before the handler: invalid requests are
rejected with a 400 VALIDATION_ERROR envelope, and the parsed + coerced data is
handed to the handler via getValidated. Handlers never re-parse the body, check
for a missing :id, or coerce query strings by hand.
// src/api/routes/posts/[id]/validator.ts
import type { RouteValidator } from "@damatjs/framework/router";
import { PostsParamsSchema, updatePostsSchema } from "@blog/types";
export const validators: RouteValidator[] = [
{ method: "GET", params: PostsParamsSchema },
{ method: "PATCH", params: PostsParamsSchema, body: updatePostsSchema },
{ method: "DELETE", params: PostsParamsSchema },
];
// src/api/routes/posts/[id]/api.ts
import { getValidated, type RouteHandler } from "@damatjs/framework/router";
import type { PostsParams, UpdatePosts } from "@blog/types";
export const PATCH: RouteHandler = async (c) => {
const { id } = getValidated<PostsParams>(c, "params"); // already validated
const data = getValidated<UpdatePosts>(c, "body");
// ...call the workflow with id + data...
};
codegen scaffolds exactly this shape for every table — the *ParamsSchema /
new*Schema / update*Schema / *QuerySchema it generates into @<module>/types
are wired into the route's validators, and the handlers read them with
getValidated. route.ts re-exports the handlers, validators, and middleware
so the framework can mount them.
Combine routes with module services via getModule and add cross-cutting
middleware in src/api/middleware/. Routing, the scanner, and middleware are
documented in
@damatjs/framework → router internals.