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:
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:
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
Document All Routes
Use describeRoute for every public endpoint:app.get("/endpoint", describeRoute({ /* docs */ }), handler)
Provide Examples
Include realistic examples in your Zod schemas:z.string().meta({ example: "real-data@example.com" })
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()`
}]
Organize with Tags
Group related endpoints:describeRoute({ tags: ["Users"], ... })
describeRoute({ tags: ["Payments"], ... })
Resources