MuseMVP Docs
API

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

Write failing tests first; update schema/queries in packages/db/src/ with db first, then bind them in app database wrappers. Orchestration belongs in src/modules/<feature>/lib/.

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.

/** New file: 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.coerce.number().int().min(1).max(100).default(10),
  offset: z.coerce.number().int().min(0).default(0),
});

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 the owning feature service using validated input. */
      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);
/** Preserve the existing route registrations. */

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:

/** New file: 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 query = {
        ...(params?.limit !== undefined ? { limit: String(params.limit) } : {}),
        ...(params?.offset !== undefined ? { offset: String(params.offset) } : {}),
      };
      const res = await apiClient.example.$get({ query });
      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/assets/router.ts
  • Admin route: admin/users.ts — adminMiddleware, query validation, describeRoute
  • Public route: contact/router.ts — localeMiddleware, form validation
  • Auth route: assets/router.ts — authMiddleware, query + json validation, describeRoute

Add unit/API contracts for unauthenticated access, permissions, invalid input, and errors. Update tests/e2e/SCENARIOS.md for new browser journeys, synchronize app/API copies, and run pnpm apps:parity.

On this page