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.
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→parameterswithin: "path", each one required; a{name}of the path that noparamsmodel describes is a string parameterquery→parameterswithin: "query"; an object parameter getsstyle: "deepObject", as the server readsfilter[status]=soldbody→requestBodywith theapplication/json(or customcontentType) content typeoutput→responseswith status200andapplication/jsoncontent typeiteration→responseswith status200andapplication/jsonlcontent type, with an example of three lines; the server sendstext/plainunless theAcceptheader includesapplication/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.
// @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.
// @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 clientimport { openapi } from '@/client/admin/openapi.ts'; // segmented clientYou 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.
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:
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 ./publicOn 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.
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.