Skip to main content

Overview

ZeroStarter uses Scalar to provide beautiful, interactive API documentation automatically generated from your Hono routes using OpenAPI specifications. The documentation is type-safe and updates automatically as you modify your API.

Features

  • Auto-Generated Docs: OpenAPI spec generated from Hono routes
  • Interactive UI: Beautiful API reference with Scalar
  • Type-Safe: Zod schemas automatically converted to OpenAPI
  • Code Samples: Built-in examples using hono/client
  • Better Auth Integration: Automatic documentation for auth endpoints
  • Live API Explorer: Test endpoints directly in the browser

Accessing Documentation

Once your API is running, access the documentation at:
  • API Reference: http://localhost:4000/api/docs
  • OpenAPI JSON: http://localhost:4000/api/openapi.json
  • Better Auth Docs: http://localhost:4000/api/auth/reference

Configuration

Scalar Setup

Scalar is configured in api/hono/src/index.ts:
api/hono/src/index.ts
import { Scalar } from "@scalar/hono-api-reference"
import { openAPIRouteHandler } from "hono-openapi"

const routes = app
  .basePath("/api")
  // ... your routes ...
  .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`,
        },
      },
    }),
  )
  .get(
    "/docs",
    Scalar({
      pageTitle: "API Reference | ZeroStarter",
      defaultHttpClient: {
        targetKey: "js",
        clientKey: "hono/client",
      },
      defaultOpenAllTags: true,
      expandAllResponses: true,
      url: "/api/openapi.json",
    }),
  )

Scalar Options

  • pageTitle: Browser tab title for the documentation page
  • defaultHttpClient: Sets hono/client as the default code sample language
  • defaultOpenAllTags: Expands all endpoint groups by default
  • expandAllResponses: Shows all response schemas expanded
  • url: Path to the OpenAPI JSON specification

Documenting Routes

Use describeRoute to add OpenAPI documentation to your Hono routes:
import { describeRoute, resolver } from "hono-openapi"
import { z } from "zod"

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: "development" }),
                  message: z.string().meta({ example: "ok" }),
                  version: z.string().meta({ example: "1.0.0" }),
                }),
              }),
            ),
          },
        },
      },
    },
  }),
  (c) => {
    const data = { message: "ok", version: BUILD_VERSION, environment: env.NODE_ENV }
    return c.json({ data })
  },
)

Adding Code Samples

Include custom code samples in your documentation:
api/hono/src/routers/v1.ts
import { describeRoute, resolver } from "hono-openapi"
import { z } from "zod"

app.get(
  "/session",
  describeRoute({
    tags: ["v1"],
    description: "Get current session only",
    ...{
      "x-codeSamples": [
        {
          lang: "typescript",
          label: "hono/client",
          source: `import { apiClient } from "@/lib/api/client"

const response = await apiClient.v1.session.$get()
const { data } = await response.json()`,
        },
      ],
    } as object,
    responses: {
      200: {
        description: "OK",
        content: {
          "application/json": {
            schema: resolver(z.object({ data: sessionSchema })),
          },
        },
      },
    },
  }),
  (c) => {
    const session = c.get("session")
    return c.json({ data: session })
  },
)

Schema Examples

Define reusable Zod schemas with examples for better documentation:
api/hono/src/routers/v1.ts
const userSchema = z.object({
  createdAt: z.string().meta({ format: "date-time", example: "2025-12-17T14:33:40.317Z" }),
  email: z.string().meta({ example: "user@example.com" }),
  emailVerified: z.boolean().meta({ example: true }),
  id: z.string().meta({ example: "iO8PZYiiwR6e0o9XDtqyAmUemv1Pc8tc" }),
  image: z.string().nullable().meta({ example: "https://example.com/avatar.png" }),
  name: z.string().meta({ example: "John Doe" }),
  updatedAt: z.string().meta({ format: "date-time", example: "2025-12-17T14:33:40.317Z" }),
})

const sessionSchema = z.object({
  id: z.string().meta({ example: "6kpGKXeJAKfB4MERWrfdyFdKd1ZB0Czo" }),
  token: z.string().meta({ example: "Ds8MdODZSgu57rbR8hzapFlcv6IwoIgD" }),
  userId: z.string().meta({ example: "iO8PZYiiwR6e0o9XDtqyAmUemv1Pc8tc" }),
  ipAddress: z.string().nullable().meta({ example: "202.9.121.21" }),
  userAgent: z.string().nullable().meta({ example: "Mozilla/5.0 Chrome/143.0.0.0" }),
  expiresAt: z.string().meta({ format: "date-time", example: "2026-01-28T13:06:25.712Z" }),
  createdAt: z.string().meta({ format: "date-time", example: "2026-01-21T13:06:25.712Z" }),
  updatedAt: z.string().meta({ format: "date-time", example: "2026-01-21T13:06:25.712Z" }),
})

Route Organization

Organize routes using tags for better documentation structure:
// System endpoints
app.get("/health", describeRoute({ tags: ["System"], ... }), handler)

// v1 API endpoints
app.get("/v1/user", describeRoute({ tags: ["v1"], ... }), handler)
app.get("/v1/session", describeRoute({ tags: ["v1"], ... }), handler)

// Better Auth endpoints (automatically tagged)
app.route("/auth", authRouter)

Type-Safe Client

The OpenAPI documentation reflects the type-safe hono/client used in the frontend:
import { hc } from "hono/client"
import type { AppType } from "@api/hono"

const apiClient = hc<AppType>(env.NEXT_PUBLIC_API_URL)

// Fully typed API calls
const response = await apiClient.v1.session.$get()
const { data } = await response.json() // TypeScript knows the exact shape
The AppType is exported from your Hono app and provides end-to-end type safety between frontend and backend.

Better Auth Documentation

Better Auth provides its own OpenAPI documentation at /api/auth/reference:
api/hono/src/index.ts
import { openAPI as openAPIPlugin } from "better-auth/plugins"

export const auth = betterAuth({
  plugins: [
    openAPIPlugin(), // Enables /api/auth/reference
    // ...
  ],
  // ...
})
This includes documentation for all auth endpoints:
  • Sign in/sign up
  • OAuth callbacks
  • Session management
  • Organization and team operations

Customization

Custom Styling

Scalar supports theme customization:
Scalar({
  // ... other options ...
  theme: "purple",
  customCss: `
    .scalar-app {
      --scalar-color-1: #your-brand-color;
    }
  `,
})

Security Schemes

Add authentication to your OpenAPI spec:
openAPIRouteHandler(app, {
  documentation: {
    info: { /* ... */ },
    components: {
      securitySchemes: {
        bearerAuth: {
          type: "http",
          scheme: "bearer",
        },
      },
    },
    security: [{ bearerAuth: [] }],
  },
})

Best Practices

1

Document All Routes

Use describeRoute for every public endpoint:
app.get("/endpoint", describeRoute({ /* docs */ }), handler)
2

Provide Examples

Include realistic examples in your Zod schemas:
z.string().meta({ example: "real-data@example.com" })
3

Add Code Samples

Show hono/client usage for each endpoint:
"x-codeSamples": [{
  lang: "typescript",
  label: "hono/client",
  source: `import { apiClient } from "@/lib/api/client"

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

Organize with Tags

Group related endpoints:
describeRoute({ tags: ["Users"], ... })
describeRoute({ tags: ["Payments"], ... })

Resources