RPC for NestJS
Install dependencies
npm i vovk-client
npm i vovk-cli@draft -D
Expose OpenAPI spec in your NestJS app
At this case we expose it at /api-ref
endpoint and Swagger UI 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
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
and run it
The NestJS server should be running at this moment on http://localhost:3000 . Set --openapi-root-url
to your server URL if it’s different.
{
"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"
}
}
npm run generate
You can optionally run start:dev
and generate
scripts (with --watch
flag) in parallel using concurrently
package.
{
"scripts": {
// ...
"dev": "concurrently \"npm run start:dev\" \"sleep 5 && npm run generate -- --watch\" --kill-others"
}
}
npm run dev
For more flexibility it’s recommended to create a config file (see below).
As you may notice, --openapi-get-module-name
and --openapi-get-method-name
are set to nestjs-operation-id
. This strategy assumes that operationId
in your OpenAPI spec is defined as XxxController_methodName
, e.g. UserController_createUser
. This is the default behavior of @nestjs/swagger
package. XxxController
is turned into an RPC module name XxxRPC
and methodName
is turned into a method name as is. If you want to change this behavior, you can use config file to define getModuleName
and getMethodName
functions manually.
Import and use
Import the resulting 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 flags are too long, or if you want to use additioannal Vovk.ts features, create a config file in the project root:
/** @type {import('vovk-cli').VovkConfig} */
const config = {
generatorConfig: {
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;
At this case the generate script can be simplified:
{
"scripts": {
// ...
"generate": "vovk g"
}
}
Configure fetcher
In order to configure authorization headers, add custom options to the generated client, or transform the response, you can create a custom fetcher (requires config file).
import { UserRPC } from 'vovk-client';
await UserRPC.updateUser({
// ...
successMessage: 'User updated successfully',
useAuthorization: true,
somethingCustom: 'customValue',
});
Enable client-side validation
The client-side validation isn’t enabled by default. For a NestJS app there are 2 options:
- Validate with the emitted JSON schema with vovk-ajv library.
- Validate with DTOs using vovk-dto/validateOnClient.
vovk-ajv
npm i vovk-ajv@draft
/** @type {import('vovk').VovkConfig} */
const config = {
generatorConfig: {
imports: {
validateOnClient: 'vovk-ajv',
},
},
};
export default config;
For more information see the vovk-ajv section.
vovk-dto/validateOnClient
npm i vovk-dto@draft
/** @type {import('vovk').VovkConfig} */
const config = {
generatorConfig: {
imports: {
validateOnClient: 'vovk-dto/validateOnClient',
},
},
};
export default config;
For more information see the vovk-dto/validateOnClient section.
Function calling
As any other RPC module, the generated NestJS RPC modules can be turned into LLM tools, invoking the server methods thru HTTP protocol.
For more information see the function calling page.