Defining New Routes
Step-by-step guide on how to create a new API endpoint with validation and OpenAPI metadata.
This guide walks you through adding a new API route in MuseMVP, following the existing patterns for validation, middleware, and OpenAPI documentation.
Step-by-Step Flow
Add or update database schema and query functions in src/backend/database/.
Create a new Hono router or extend an existing one in src/backend/api/routes/.
Register the router in src/backend/api/app.ts.
Add API client hooks in src/backend/api-client/ for frontend consumption.
1. Define the Route Handler
Create a new router file or add to an existing one. Use Zod for validation and describeRoute for OpenAPI metadata.
// src/backend/api/routes/example/router.ts
import { Hono } from "hono";
import { describeRoute, resolver, validator } from "hono-openapi";
import { z } from "zod";
import { authMiddleware } from "@/backend/api/middleware/auth";
const querySchema = z.object({
limit: z.string().optional().default("10").transform(Number),
offset: z.string().optional().default("0").transform(Number),
});
const responseSchema = z.object({
items: z.array(z.object({ id: z.string(), name: z.string() })),
total: z.number(),
});
export const exampleRouter = new Hono()
.basePath("/example")
.use(authMiddleware)
.get(
"/",
validator("query", querySchema),
describeRoute({
tags: ["Example"],
summary: "List items",
responses: {
200: {
description: "Items returned",
content: {
"application/json": {
schema: resolver(responseSchema),
},
},
},
},
}),
async (c) => {
const { limit, offset } = c.req.valid("query");
const user = c.get("user");
// Call query layer...
return c.json({ items: [], total: 0 });
},
);2. Register the Router
Add the new router to the main app in src/backend/api/app.ts:
import { exampleRouter } from "./routes/example/router";
const appRouter = app
.route("/", authRouter)
.route("/", exampleRouter) // Add this line
// ... other routes3. Validation Patterns
| Location | Use Case | Example |
|---|---|---|
validator("query", schema) | Query params | Pagination, search |
validator("json", schema) | JSON body | Create/update payloads |
validator("form", schema) | Form data | Contact form, file upload |
validator("param", schema) | Path params | /conversations/:id |
4. Middleware Options
| Middleware | When to Use |
|---|---|
authMiddleware | Requires logged-in user; sets c.get("user") and c.get("session") |
adminMiddleware | Requires admin role; use for admin-only endpoints |
localeMiddleware | Sets c.get("locale") from request/cookie for i18n |
5. API Client Hook
Create a TanStack Query hook that calls your new endpoint:
// src/backend/api-client/example/use-example.ts
import { useQuery } from "@tanstack/react-query";
import { apiClient } from "@/backend/api-client/api-client";
export function useExample(params?: { limit?: number; offset?: number }) {
return useQuery({
queryKey: ["example", params],
queryFn: async () => {
const res = await apiClient.example.$get({ query: params });
if (!res.ok) throw new Error("Fetch failed");
return res.json();
},
});
}Quick Reference
- Admin route:
admin/users.ts—adminMiddleware, query validation,describeRoute - Public route:
contact/router.ts—localeMiddleware, form validation - Auth route:
aichat/router.ts—authMiddleware, param + json validation