HTTP routes live in src/routes/<domain>/index.ts. Each file exports a Hono route chain that gets mounted in src/routes/index.ts.
import { createProjectSchema } from "@backend/features/projects/db/mutations/create-project.mutation";
import { createProject } from "@backend/features/projects/modules/create-project";
import { ApplicationError, ApplicationErrorCode } from "@backend/middleware/error";
import { hasAccess } from "@backend/middleware/permissions/has-access";
import { validate } from "@backend/middleware/validation/validate";
import { getAuthenticatedApp } from "@backend/utils/auth/auth-hono";
type ProjectMutationResponse = { projectId: string };
export const projectRoutes = getAuthenticatedApp()
.get("/", hasAccess({ action: "read", subject: "project" }), async (ctx) => {
const { organizationId } = ctx.var;
const response = await getProjectsList({ organizationId });
return ctx.json(response);
})
.post(
"/",
hasAccess({ action: "write", subject: "project" }),
validate("json", createProjectSchema),
async (ctx) => {
const body = ctx.req.valid("json");
const { projectId } = await createProject({ data: body });
return ctx.json<ProjectMutationResponse>({ projectId });
},
);
Mount routes in src/routes/index.ts:
import { projectRoutes } from "./projects";
export const registerRoutes = (app: Hono) => {
return app
.route("/projects", projectRoutes)
// ... other routes
};
The return type of registerRoutes is what generates the ApiRoutesSdk type for the frontend. Adding a route here automatically exposes it on the frontend SDK.
Every route that accepts input must use the validate() middleware:
// Body validation
validate("json", createProjectSchema)
// Query param validation
validate("query", filtersSchema)
Read validated data with ctx.req.valid("json") or ctx.req.valid("query"). Never use ctx.req.json() directly, since it bypasses validation and breaks the type bridge.
getAuthenticatedApp() creates a route group that requires authenticationhasAccess({ action, subject }) checks user permissions before the handler runsnew Hono() insteadsrc/routes/<domain>/index.tsgetAuthenticatedApp() with validate() middlewaresrc/routes/index.ts with app.route("/prefix", routes)