Skip to main content
Alpha

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

What you need before you start
NameTypeDescription
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:

Routes added by the openapi option
NameTypeDescription
/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):

OpenAPIConfig (createOpenAPISpec)
NameTypeDescription
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' }.
ScalarDocsOptions (createScalarDocs)
NameTypeDescription
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'.

In-production pitfalls

See also

Services & contracts