MuseMVP Docs
API Design

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 routes

3. Validation Patterns

LocationUse CaseExample
validator("query", schema)Query paramsPagination, search
validator("json", schema)JSON bodyCreate/update payloads
validator("form", schema)Form dataContact form, file upload
validator("param", schema)Path params/conversations/:id

4. Middleware Options

MiddlewareWhen to Use
authMiddlewareRequires logged-in user; sets c.get("user") and c.get("session")
adminMiddlewareRequires admin role; use for admin-only endpoints
localeMiddlewareSets 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

src/backend/api/routes/admin/users.ts
src/backend/api/routes/contact/router.ts
src/backend/api/routes/aichat/router.ts
  • Admin route: admin/users.tsadminMiddleware, query validation, describeRoute
  • Public route: contact/router.tslocaleMiddleware, form validation
  • Auth route: aichat/router.tsauthMiddleware, param + json validation