Expose OpenAPI & Scalar
Turn on the generated OpenAPI document and the interactive Scalar API
reference UI for an existing oRPC service — as a one-line defineService option
or by mounting the Layer-1 primitives onto any Hono app yourself.
Prerequisites
| Name | Type | Description |
|---|---|---|
An oRPC service |
router |
An existing service with an oRPC router whose contract inputs/outputs are zod schemas. See the Services hub or the Build-a-service tutorial. |
@netscript/service |
package |
Already a dependency of every generated service. Exposes defineService, the createService builder, and the createOpenAPISpec / createScalarDocs / createScalarJs primitives. |
zod schemas |
contract |
Spec generation reads your contract's zod schemas via the ZodToJsonSchemaConverter; routes without zod input/output produce an empty schema for that operation. |
Steps
1. Turn it on with defineService (the one-line path)
Pass an openapi block to the preset. That single option enables the generated
OpenAPI JSON spec, the Scalar docs page, and the bundled Scalar JS — no extra
imports.
// services/users/src/main.ts
import { defineService } from '@netscript/service';
import { router } from './router.ts';
await defineService(router, {
name: 'users',
version: '1.0.0',
port: parseInt(Deno.env.get('PORT') || '3001'),
openapi: {
title: 'Users API',
description: 'User management service',
},
});
This wires three routes onto the service:
| Name | Type | Description |
|---|---|---|
/api/openapi.json |
GET → JSON |
The machine-readable OpenAPI document, generated from your contract's zod schemas. |
/api/docs |
GET → HTML |
The Scalar interactive API reference UI; it fetches the spec above and renders try-it docs. |
/api/docs/scalar.js |
GET → JS |
The locally bundled Scalar runtime, so the docs UI works offline with no CDN dependency. |
2. Hit the endpoints
With the service running (directly via deno task --cwd services/users dev, or
under aspire start for DB-backed services), open the docs UI and fetch the raw
spec:
# from a shell — confirm both surfaces answer
curl http://localhost:3001/api/openapi.json # raw OpenAPI document
open http://localhost:3001/api/docs # Scalar reference UI in a browser
The REST surface generated from the same contract is served under /api/*, and
the typed oRPC endpoint stays at /api/rpc/* — the spec describes the former.
3. (Optional) Hand-wire the primitives onto a host app
When you need finer control — a custom path, a different theme, or mounting onto
an existing Hono app that is not a defineService runtime — drop down to the
three Layer-1 primitives. createOpenAPISpec serves the spec JSON,
createScalarDocs serves the Scalar HTML, and createScalarJs serves the
bundled runtime so the UI loads offline.
// services/users/src/main.ts
import { defineService } from '@netscript/service';
import { router } from './router.ts';
// One option turns on the spec, the Scalar UI, and the bundled JS.
await defineService(router, {
name: 'users',
version: '1.0.0',
port: 3001,
openapi: {
title: 'Users API',
description: 'User management service',
},
});
// services/users/src/main.ts — step-by-step, with a custom spec URL
import { createService } from '@netscript/service';
import { router } from './router.ts';
await createService(router, { name: 'users', version: '1.0.0', port: 3001 })
.withCors()
.withLogger()
.withOpenAPI({ title: 'Users API', description: 'User management service' })
.withDocs() // optional: .withDocs({ specUrl: '/api/openapi.json' })
.withRPC()
.withHealth()
.serve();
// host/openapi-routes.ts — wire the primitives onto an existing Hono app
import {
createOpenAPISpec,
createScalarDocs,
createScalarJs,
} from '@netscript/service';
import { router } from './router.ts';
// 1. Serve the OpenAPI JSON document.
app.get('/api/openapi.json', createOpenAPISpec(router, {
title: 'Users API',
version: '1.0.0',
description: 'User management service',
servers: [{ url: '/api', description: 'local' }],
}));
// 2. Serve the Scalar UI that loads that spec.
app.get('/api/docs', createScalarDocs({
specUrl: '/api/openapi.json',
title: 'Users API',
theme: 'kepler',
}));
// 3. Serve the bundled Scalar runtime (offline, no CDN).
app.get('/api/docs/scalar.js', createScalarJs());
Option keys
The primitives accept these confirmed option shapes (@netscript/service):
| Name | Type | Description |
|---|---|---|
title |
string (required) |
API title shown in the spec and the Scalar UI. |
version |
string (required) |
API version string, e.g. '1.0.0'. |
description |
string? |
Longer API description rendered in the docs. |
servers |
Array<{ url; description? }>? |
Server URLs advertised in the spec; defaults to a single entry { url: '/api' }. |
| Name | Type | Description |
|---|---|---|
specUrl |
string (required) |
URL the Scalar UI fetches the OpenAPI document from, e.g. '/api/openapi.json'. |
title |
string? |
Docs page title; defaults to 'API Documentation'. |
theme |
'default' | 'kepler' | 'moon' | 'purple' | 'saturn' |
Scalar UI theme; defaults to 'kepler'. |