Skip to Content
OpenAPI Specification

OpenAPI Specification and @operation Decorator

Vovk.ts generates an OpenAPI specification from the procedures that have an operation object, using validation models to populate it with parameters, requestBody, and responses. The @operation decorator gives a procedure its operation object and enriches it with metadata such as summary, description, tags, and more; @operation.tool and @operation.error give it one too. A procedure without any of them is left out of the specification, and deriveTools makes no tool of it either. The decorator accepts OperationObject type from openapi3-ts/oas31 , enhanced with Vovk-specific x-tool property related to deriveTools function.

src/modules/user/user-controller.ts
import { procedure, put, prefix, operation } from 'vovk'; import { z } from 'zod'; @prefix('users') export default class UserController { @operation({ summary: 'Update User', description: 'Update user information', }) @put('{id}') static updateUser = procedure({ // ... }); }

The validation models, accepted by the procedure are converted to OpenAPI operation objects according to the following mapping:

  • params → parameters with in: "path", each one required; a {name} of the path that no params model describes is a string parameter
  • query → parameters with in: "query"; an object parameter gets style: "deepObject", as the server reads filter[status]=sold
  • body → requestBody with the application/json (or custom contentType) content type
  • output → responses with status 200 and application/json content type
  • iteration → responses with status 200 and application/jsonl content type, with an example of three lines; the server sends text/plain unless the Accept header includes application/jsonl

A schema with an id, such as a Zod schema with .meta({ id }), goes to components.schemas. A name OpenAPI doesn’t allow has its other characters replaced with _ (User Profile becomes User_Profile), and a schema whose name another schema took, one of openAPIObject.components included, gets the RPC module, procedure and slot names in front: UserRPCCreateUserBodyUser.

Configuring the OpenAPI Specification

The OpenAPI specification can be configured in the vovk.config file under the outputConfig.openAPIObject option. This object is merged with the generated specification, allowing you to set global properties such as info, servers, and more.

vovk.config.js
// @ts-check /** @type {import('vovk').VovkConfig} */ const config = { outputConfig: { openAPIObject: { info: { title: 'My app API', description: 'API for My App hosted at https://myapp.example.com/.', license: { name: 'MIT', url: 'https://opensource.org/licenses/MIT', }, version: '1.0.0', }, servers: [ { url: 'https://myapp.example.com', description: 'Production', }, { url: 'http://localhost:3000', description: 'Localhost', }, ], }, }, }; module.exports = config;

The openAPIObject can also be configured individually for each segment using outputConfig.segments.[segmentName].openAPIObject.

vovk.config.js
// @ts-check /** @type {import('vovk').VovkConfig} */ const config = { outputConfig: { segments: { admin: { openAPIObject: { info: { title: 'Admin API', description: 'API for Admin segment.', version: '1.0.0', }, }, }, }, }, };

Utilizing the OpenAPI Specification

The generated RPC client exports an openapi object from openapi module that contains the full back-end specification for the composed client. When using the segmented client, each segment also exports its own specification.

import { openapi } from '@/client/openapi'; // composed client
import { openapi } from '@/client/admin/openapi.ts'; // segmented client

You can use the specification directly as a variable or expose it via a static segment with a simple controller that serves it as a JSON endpoint.

src/modules/static/openapi/openapi-controller.ts
import { get, operation } from 'vovk'; import { openapi } from '@/client/openapi'; export default class OpenApiController { @get('openapi.json') static getSpec = () => openapi; }

If you prefer to skip Vovk entirely for the spec endpoint, a plain Next.js route handler works just as well. This avoids registering a controller and keeps the docs route outside the generated schema:

src/app/openapi.json/route.ts
import { openapi } from '@/client/openapi'; export const GET = () => Response.json(openapi);

You can also emit openapi.json as a standalone file with the openapiJson template, without generating a client. Useful for serving the spec as a static asset or handing it to another tool:

npx vovk generate --from openapiJson --out ./public

On the client side, you can use any OpenAPI documentation generator. Scalar  is a recommended choice as Vovk.ts generates code snippets for the generated RPC modules.

import { ApiReferenceReact } from "@scalar/api-reference-react"; import "@scalar/api-reference-react/style.css"; async function App() { return ( <ApiReferenceReact configuration={{ url: "/api/static/openapi.json" }} /> ); } export default App;

For a live demonstration, see the “Hello World” application spec . Check “Hello World” page for details.


The @operation decorator also provides tool property that defines tool-specific attributes for deriveTools function. It’s set under x-tool key in the OpenAPI operation object.

src/modules/user/user-controller.ts
import { procedure, put, operation } from 'vovk'; export default class UserController { @operation.tool({ name: 'update_user', description: 'Update user information in the system', }) @operation({ summary: 'Update User', description: 'Update user information', }) @put('{id}') static updateUser = procedure({ // ... }); }

For more details, see the deriveTools documentation.

Last updated on