Route Structure
Each route group follows a consistent three-file pattern:{resource}.routes.ts- OpenAPI route definitions with request/response schemas{resource}.handlers.ts- Request handlers with business logic{resource}.index.ts- Router that connects routes to handlers
Creating a New Route Group
Create a new file for your route definitions. Import the necessary helpers from
@hono/zod-openapi and stoker.import { createRoute, z } from "@hono/zod-openapi";
import * as HttpStatusCodes from "stoker/http-status-codes";
import { jsonContent, jsonContentRequired } from "stoker/openapi/helpers";
import { createErrorSchema, IdParamsSchema } from "stoker/openapi/schemas";
import { insertTasksSchema, selectTasksSchema } from "@/db/schema";
import { notFoundSchema } from "@/lib/constants";
const tags = ["Tasks"];
export const list = createRoute({
path: "/tasks",
method: "get",
tags,
responses: {
[HttpStatusCodes.OK]: jsonContent(
z.array(selectTasksSchema),
"The list of tasks",
),
},
});
export const create = createRoute({
path: "/tasks",
method: "post",
request: {
body: jsonContentRequired(
insertTasksSchema,
"The task to create",
),
},
tags,
responses: {
[HttpStatusCodes.OK]: jsonContent(
selectTasksSchema,
"The created task",
),
[HttpStatusCodes.UNPROCESSABLE_ENTITY]: jsonContent(
createErrorSchema(insertTasksSchema),
"The validation error(s)",
),
},
});
import { eq } from "drizzle-orm";
import * as HttpStatusCodes from "stoker/http-status-codes";
import * as HttpStatusPhrases from "stoker/http-status-phrases";
import type { AppRouteHandler } from "@/lib/types";
import type { ListRoute, CreateRoute } from "./tasks.routes";
import db from "@/db";
import { tasks } from "@/db/schema";
export const list: AppRouteHandler<ListRoute> = async (c) => {
const tasks = await db.query.tasks.findMany();
return c.json(tasks);
};
export const create: AppRouteHandler<CreateRoute> = async (c) => {
const task = c.req.valid("json");
const [inserted] = await db.insert(tasks).values(task).returning();
return c.json(inserted, HttpStatusCodes.OK);
};
The
c.req.valid() method returns the validated request data based on your route schema. No manual validation needed!import { createRouter } from "@/lib/create-app";
import * as handlers from "./tasks.handlers";
import * as routes from "./tasks.routes";
const router = createRouter()
.openapi(routes.list, handlers.list)
.openapi(routes.create, handlers.create)
.openapi(routes.getOne, handlers.getOne)
.openapi(routes.patch, handlers.patch)
.openapi(routes.remove, handlers.remove);
export default router;
Route Patterns
List Resources (GET)
Create Resource (POST)
Get Single Resource (GET)
Update Resource (PATCH)
Delete Resource (DELETE)
Handler Patterns
Accessing Validated Data
Returning Responses
Error Handling
Type Safety
The route definitions provide end-to-end type safety:The
AppRouteHandler type provides type inference for request validation and response data based on your route definition.OpenAPI Documentation
Your routes automatically appear in the OpenAPI documentation at/reference. The schemas you define in your routes are used to generate:
- Request/response examples
- Validation rules
- Type definitions for API clients
- Interactive API testing UI
Next Steps
Database
Learn how to define schemas with Drizzle ORM
Validation
Understand request/response validation with Zod
Testing
Write tests for your routes
