Skip to main content

Route Organization

Routes are organized into modular routers mounted at different base paths:
const routes = app
  .basePath("/api")
  .route("/auth", authRouter)
  .route("/v1", v1Router)
This creates the following URL structure:
  • /api/auth/* - Authentication endpoints (Better Auth)
  • /api/v1/* - Protected API endpoints (require authentication)
  • /api/health - Health check endpoint
  • /api/docs - API documentation (Scalar)
  • /api/openapi.json - OpenAPI specification

System Routes

Root-level routes for system information (index.ts:34-44):
app
  .get("/", (c) => {
    const data = { version: BUILD_VERSION, environment: env.NODE_ENV }
    return c.json({ data })
  })
  .get("/headers", (c) => {
    if (env.NODE_ENV !== "local" && env.NODE_ENV !== "development") {
      return c.json({ error: { code: "FORBIDDEN", message: "Forbidden" } }, 403)
    }
    const data = c.req.header()
    return c.json({ data })
  })

Health Check Endpoint

Documented health check with OpenAPI metadata (index.ts:46-88):
app.get(
  "/health",
  describeRoute({
    tags: ["System"],
    description: "Get the system health",
    responses: {
      200: {
        description: "OK",
        content: {
          "application/json": {
            schema: resolver(
              z.object({
                data: z.object({
                  environment: z
                    .enum(["local", "development", "test", "staging", "production"])
                    .meta({ example: env.NODE_ENV }),
                  message: z.string().meta({ example: "ok" }),
                  version: z.string().meta({ example: BUILD_VERSION }),
                }),
              }),
            ),
          },
        },
      },
    },
  }),
  (c) => {
    const data = { message: "ok", version: BUILD_VERSION, environment: env.NODE_ENV }
    return c.json({ data })
  },
)

Authentication Router

The auth router delegates all routes to Better Auth (routers/auth.ts:4-6):
export const authRouter = new Hono()
  .get("/get-session", (c) => auth.handler(c.req.raw))
  .on(["GET", "POST"], "/*", (c) => auth.handler(c.req.raw))
This creates endpoints like:
  • GET /api/auth/get-session - Retrieve current session
  • POST /api/auth/sign-in/email - Email sign-in
  • POST /api/auth/sign-up/email - Email sign-up
  • And all other Better Auth endpoints

Protected API Router (v1)

The v1 router requires authentication for all routes (routers/v1.ts:29-32):
export const v1Router = new Hono<{
  Variables: Session
}>()
  .use("/*", authMiddleware)

Session Endpoint

Returns the current authenticated session (routers/v1.ts:33-65):
.get(
  "/session",
  describeRoute({
    tags: ["v1"],
    description: "Get current session only",
    responses: {
      200: {
        description: "OK",
        content: {
          "application/json": {
            schema: resolver(z.object({ data: sessionSchema })),
          },
        },
      },
    },
  }),
  (c) => {
    const data = c.get("session")
    return c.json({ data })
  },
)

User Endpoint

Returns the current authenticated user (routers/v1.ts:66-98):
.get(
  "/user",
  describeRoute({
    tags: ["v1"],
    description: "Get current user only",
    responses: {
      200: {
        description: "OK",
        content: {
          "application/json": {
            schema: resolver(z.object({ data: userSchema })),
          },
        },
      },
    },
  }),
  (c) => {
    const data = c.get("user")
    return c.json({ data })
  },
)

OpenAPI Documentation

The API automatically generates OpenAPI specification (index.ts:91-105):
.get(
  "/openapi.json",
  openAPIRouteHandler(app, {
    documentation: {
      info: {
        version: BUILD_VERSION,
        title: "ZeroStarter",
        description: `API Reference for your ZeroStarter Instance.
- [Dashboard](/dashboard) - Client-side dashboard application
- [Better Auth Instance](/api/auth/reference) - Better Auth API reference
- [hono/client](/core-concepts/type-safe-api) - Type-safe API client for frontend`,
      },
    },
  }),
)

Scalar API Reference

Interactive API documentation using Scalar (index.ts:106-118):
.get(
  "/docs",
  Scalar({
    pageTitle: "API Reference | ZeroStarter",
    defaultHttpClient: {
      targetKey: "js",
      clientKey: "hono/client",
    },
    defaultOpenAllTags: true,
    expandAllResponses: true,
    url: "/api/openapi.json",
  }),
)
Access the interactive documentation at http://localhost:3001/api/docs.

Code Samples in Documentation

Routes include TypeScript code samples using hono/client (index.ts:52-61):
{
  "x-codeSamples": [
    {
      lang: "typescript",
      label: "hono/client",
      source: `import { apiClient } from "@/lib/api/client"

const response = await apiClient.health.$get()
const { data } = await response.json()`,
    },
  ],
}

Type-Safe RPC Export

The routes object is exported as a type for frontend consumption (index.ts:120):
export type AppType = typeof routes
This enables end-to-end type safety when using hono/client in your frontend:
import type { AppType } from "@api/hono"
import { hc } from "hono/client"

const client = hc<AppType>("http://localhost:3001")

const response = await client.api.v1.user.$get()
const { data } = await response.json()

Adding New Routes

To add a new route:
  1. Create a new router file in routers/
  2. Define routes with describeRoute for documentation
  3. Export the router from routers/index.ts
  4. Mount it in index.ts using .route()
Example:
import { Hono } from "hono"
import { describeRoute, resolver } from "hono-openapi"
import { z } from "zod"

export const postsRouter = new Hono()
  .get(
    "/",
    describeRoute({
      tags: ["Posts"],
      description: "List all posts",
      responses: {
        200: {
          description: "OK",
          content: {
            "application/json": {
              schema: resolver(z.object({ data: z.array(postSchema) })),
            },
          },
        },
      },
    }),
    async (c) => {
      const data = await db.select().from(posts)
      return c.json({ data })
    },
  )

Next Steps