Skip to Content
RPC for NestJS

RPC for NestJS

NestJS provides built‑in OpenAPI support via @nestjs/swagger , and Vovk.ts can generate a client from an OpenAPI 3+ specification. Together, you can generate a type‑safe “RPC” client for a NestJS app with all the benefits of Vovk.ts, including client‑side validation, function calling, and more.

Install Dependencies

npm i vovk-client
npm i vovk-cli@draft -D

Expose OpenAPI Spec in Your NestJS App

In this example, the spec is exposed at /api-ref, with Swagger UI available at /docs.

src/main.ts
import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { AppModule } from './app.module'; async function bootstrap() { const appOptions = { cors: true }; const app = await NestFactory.create(AppModule, appOptions); app.setGlobalPrefix('api'); const options = new DocumentBuilder() .setTitle('NestJS Example App') .setDescription('The API description') .setVersion('1.0') .addBearerAuth() .build(); const document = SwaggerModule.createDocument(app, options); app.getHttpAdapter().get('/api-ref', (req, res) => { res.json(document); }); SwaggerModule.setup('/docs', app, document); await app.listen(3000); } bootstrap() .catch((err) => { console.log(err); });

Use OpenAPI Decorators from @nestjs/swagger 

Follow the official documentation  to decorate controllers and DTOs with OpenAPI decorators.

import { Controller, Post, Body, Query, Param, HttpStatus } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiParam, ApiBody, ApiQuery, ApiExtraModels } from '@nestjs/swagger'; import { CreateUserDto, QueryParamsDto, UserResponseDto } from './dto/user.dto'; @Controller('users') @ApiExtraModels(UserResponseDto) // Ensures the DTO appears in schemas export class UserController { @Post(':organizationId') @ApiOperation({ summary: 'Create a new user' }) @ApiParam({ name: 'organizationId', type: 'string', description: 'Organization ID', }) @ApiBody({ type: CreateUserDto, description: 'User creation data' }) @ApiQuery({ type: QueryParamsDto, required: false }) @ApiResponse({ status: HttpStatus.CREATED, description: 'User successfully created', type: UserResponseDto, // This ensures it appears in components/schemas }) async createUser( @Param('organizationId') organizationId: string, @Body() createUserDto: CreateUserDto, @Query() queryParams: QueryParamsDto ): Promise<UserResponseDto> { // ... } }

Add generate Script to package.json

Run the development server so NestJS is available at http://localhost:3000 . Set --openapi-root-url to your server URL if it differs.

package.json
{ "scripts": { // ... "start:dev": "nest start --watch", "generate": "vovk g --openapi=http://localhost:3000/api-ref --openapi-get-module-name=nestjs-operation-id --openapi-get-method-name=nestjs-operation-id --openapi-root-url=http://localhost:3000 --openapi-fallback=.openapi-cache/openapi.json" } }

Here’s what each flag does:

  • --openapi=http://localhost:3000/api-ref — URL of the OpenAPI spec.
  • --openapi-get-module-name=nestjs-operation-id — derive module names from operationId.
  • --openapi-get-method-name=nestjs-operation-id — derive method names from operationId.
  • --openapi-root-url=http://localhost:3000 — root URL of your API.
  • --openapi-fallback=.openapi-cache/openapi.json — cache the spec locally and use it if the URL is unavailable (useful in CI/CD).

The nestjs-operation-id strategy assumes operationId values like XxxController_methodName (e.g., UserController_createUser). Under this strategy, XxxController becomes XxxRPC, and methodName is used verbatim. For custom behavior, create a config file and define getModuleName/getMethodName.

Run the generate script in a separate terminal:

npm run generate

You can run start:dev and generate in parallel with concurrently . The --watch option makes the generator rebuild on spec changes.

package.json
{ "scripts": { // ... "generate": "...", "start:dev": "nest start --watch", "dev": "concurrently \"npm run start:dev\" \"sleep 5 && npm run generate -- --watch\" --kill-others" } }

The dev script runs both commands in parallel, waiting 5 seconds before generate to let NestJS start.

npm run dev

For more flexibility, create a config file as shown below.

Import and Use

Import the generated client from any fetch‑enabled environment:

import { UserRPC } from 'vovk-client'; const user = await UserRPC.createUser({ body: { name: 'John Doe', email: 'john.doe@example.com' }, query: { page: 1, limit: 10 }, params: { organizationId: 'org123' } });

Create Config File

If the CLI flags are too long or you want more features, create a config file in the project root:

vovk.config.mjs
/** @type {import('vovk-cli').VovkConfig} */ const config = { outputConfig: { segments: { nest: { openAPIMixin: { source: { url: 'http://localhost:3000/api-ref', fallback: '.openapi-cache/openapi.json', }, apiRoot: 'http://localhost:3000', getModuleName: 'nestjs-operation-id', getMethodName: 'nestjs-operation-id', }, }, }, }, }; export default config;

In this case, the generate script simplifies to:

package.json
{ "scripts": { // ... "generate": "vovk g" } }

Configure fetcher

To configure authorization headers, add custom options to the generated client, or transform the response, create a custom fetcher (requires a config file).

import { UserRPC } from 'vovk-client'; await UserRPC.updateUser({ // ... successMessage: 'User updated successfully', useAuthorization: true, somethingCustom: 'customValue', });

Enable Client-Side Validation

Client‑side validation isn’t enabled by default. For a NestJS app, choose one of two options:

vovk-ajv

npm i vovk-ajv@draft
vovk.config.mjs
/** @type {import('vovk').VovkConfig} */ const config = { outputConfig: { imports: { validateOnClient: 'vovk-ajv', }, }, }; export default config;

See the vovk-ajv section for details.

vovk-dto/validateOnClient

npm i vovk-dto@draft
vovk.config.mjs
/** @type {import('vovk').VovkConfig} */ const config = { outputConfig: { imports: { validateOnClient: 'vovk-dto/validateOnClient', }, }, }; export default config;

In this setup, generated client methods validate inputs with DTOs using class-validator  before sending requests. Transform plain objects to class instances with class-transformer :

import { UserRPC } from 'vovk-client'; import { plainToInstance } from 'class-transformer'; import { UpdateUserBodyDto, UpdateUserResponseDto } from '@/modules/user/UserDto'; const user = await UserRPC.updateUser({ body: plainToInstance(UpdateUserBodyDto, { name: 'John Doe', age: 42, } satisfies UpdateUserBodyDto), // ... same for query and params // optionally transform response data to DTO transform: (data) => plainToInstance(UpdateUserResponseDto, data) });

See the vovk-dto/validateOnClient section for more information.

Function Calling

As with any RPC module, the generated NestJS RPC modules can be turned into LLM tools and invoke server methods over HTTP.

For details, see function calling.

Last updated on