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-clientnpm i vovk-cli@draft -DExpose OpenAPI Spec in Your NestJS App
In this example, the spec is exposed at /api-ref, with Swagger UI available at /docs.
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.
{
"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 fromoperationId.--openapi-get-method-name=nestjs-operation-id— derive method names fromoperationId.--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 generateYou can run start:dev and generate in parallel with concurrently . The --watch option makes the generator rebuild on spec changes.
{
"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 devFor 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:
/** @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:
{
"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:
- Validate using the emitted JSON Schema via vovk-ajv.
- Validate with DTOs via vovk-dto/validateOnClient.
vovk-ajv
npm i vovk-ajv@draft/** @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/** @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.