Routers and Routes
Routers and routes are the core building blocks of NestRPC. This guide covers everything you need to know about defining and organizing your RPC endpoints.
🎯 Defining a Router
Create a router class with the @Router() decorator:
import { Router, Route } from '@nestjs-rpc/server';
@Router()
export class UserQueriesRouter {
@Route()
getUser({ id }: { id: string }) {
return { id, name: 'John Doe', email: 'john@example.com' };
}
@Route()
listUsers() {
return [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
];
}
}
The @Router() decorator applies @Controller() under the hood, making your class a NestJS controller. This means:
- Routers accept all NestJS controller parameter decorators (
@Req(),@Res(),@Headers(),@Body(), etc.) - Routes can have guard and pipe decorators (
@UseGuards(),@UsePipes(),@UseInterceptors(), etc.) just like native NestJS controllers - They work with dependency injection, interceptors, and all NestJS features
- They MUST be registered in the module's
controllersarray
🛣️ Defining Routes
Mark methods with @Route() to expose them as RPC endpoints:
@Router()
export class UserMutationsRouter {
@Route()
createUser({ name, email }: { name: string; email: string }) {
// Create user logic
return { id: '123', name, email };
}
@Route()
updateUser({ id, name, email }: { id: string; name?: string; email?: string }) {
// Update user logic
return { id, name, email };
}
}
📤 File Upload Routes
NestRPC has built-in support for file uploads. See the File Uploads guide for details:
@Router()
export class FilesRouter {
// Single file upload
@Route({ file: 'single' })
uploadFile(
{ description }: { description?: string },
file?: Express.Multer.File
) {
return { filename: file?.originalname, size: file?.size };
}
// Multiple file upload
@Route({ file: 'multiple' })
uploadFiles(
{ category }: { category?: string },
files?: Express.Multer.File[]
) {
return { files: files?.map(f => f.originalname), count: files?.length };
}
}
📋 Registering Routers
Create the Manifest
Use defineManifest() to map keys to routers or nested maps:
import { defineManifest } from '@nestjs-rpc/server';
import { UserQueriesRouter } from './user.queries.router';
import { UserMutationsRouter } from './user.mutations.router';
export const manifest = defineManifest({
user: {
queries: UserQueriesRouter,
mutations: UserMutationsRouter,
},
});
// 🔁 Export the type for client-side type safety
export type Manifest = typeof manifest;
Register in Module
⚠️ CRITICAL: Routers are NestJS controllers under the hood. You MUST add your router classes to the module's controllers array so Nest can instantiate them:
import { Module } from '@nestjs/common';
import { UserQueriesRouter } from './user.queries.router';
import { UserMutationsRouter } from './user.mutations.router';
@Module({
controllers: [UserQueriesRouter, UserMutationsRouter],
providers: [],
})
export class AppModule {}
Initialize Before App Creation
⚠️ CRITICAL: Call nestRpcInit(manifest) BEFORE creating your Nest app:
import { nestRpcInit } from '@nestjs-rpc/server';
async function bootstrap() {
nestRpcInit(manifest); // ✅ Must be called FIRST
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
The runtime validates that:
- The final path segment is a router class decorated with
@Router() - The method exists and is decorated with
@Route()
📥 Parameter Rules
Important parameter rules:
-
First Parameter (index 0): Reserved for the incoming request body. It's automatically injected. Do not decorate it. Its TypeScript type flows to the client automatically.
-
Second Parameter (index 1): If the route has file upload configuration (
file: 'single'orfile: 'multiple'), this parameter is reserved for the file/files (Express.Multer.FileorExpress.Multer.File[]). -
Subsequent Parameters: Can use NestJS parameter decorators (
@Req(),@Res(),@Headers(), etc.)
// No file upload - first param is body
@Route()
async getUserById(id: string) { // ✅ First param = request body
return getUserFromDb(id);
}
// No file upload - first param is body, subsequent params can use decorators
@Route()
async createUser(
{ name, email }: { name: string; email: string }, // ✅ First param = request body
@Req() req: Request, // ✅ Can use decorators for subsequent params
) {
return createUserInDb({ name, email, ip: req.ip });
}
// With file upload - first param is body, second param is file
@Route({ file: 'single' })
async uploadFile(
{ description }: { description?: string }, // ✅ First param = request body
file?: Express.Multer.File, // ✅ Second param = file (when file config is set)
@Req() req: Request, // ✅ Subsequent params can use decorators
) {
return { filename: file?.originalname, description };
}
🎨 Nested Routers
Organize your API with nested router structures:
export const manifest = defineManifest({
user: {
queries: UserQueriesRouter, // GET-like operations
mutations: UserMutationsRouter, // POST-like operations
},
files: FilesRouter,
admin: {
users: AdminUsersRouter,
settings: AdminSettingsRouter,
},
});
Routes become:
POST /nestjs-rpc/user/queries/getUserPOST /nestjs-rpc/user/mutations/createUserPOST /nestjs-rpc/files/uploadFilePOST /nestjs-rpc/admin/users/listAll
Client Pathing
The nested keys you choose define the access path on the client:
// Client usage
const { data: user } = await rpc.user.queries.getUser({ id: '1' });
await rpc.user.mutations.createUser({ name: 'John', email: 'john@example.com' });
await rpc.files.uploadFile({ description: 'Avatar' }, { file: myFile });
const { data: all } = await rpc.admin.users.listAll();
🔧 Advanced Usage
Dependency Injection
Routers are NestJS controllers, so you can use dependency injection:
@Router()
export class UserRouter {
constructor(
private readonly userService: UserService,
private readonly db: DatabaseService,
) {}
@Route()
async getUserById(id: string) {
return this.userService.findById(id);
}
}
Custom Parameter Decorators
You can use NestJS parameter decorators for subsequent parameters:
import { Router, Route } from '@nestjs-rpc/server';
import { Req, Headers, Body } from '@nestjs/common';
import type { Request } from 'express';
@Router()
export class UserRouter {
@Route()
updateUser(
{ id, name }: { id: string; name: string }, // ✅ First param = input
@Req() req: Request, // ✅ Can use decorators
@Headers('x-trace') trace?: string, // ✅ Can use decorators
) {
return { id, name, updatedBy: req.ip, trace };
}
}
Custom Router Parameter Decorators
Create your own decorators using createRouterParamDecorator:
import { createRouterParamDecorator } from '@nestjs-rpc/server';
import { ExecutionContext } from '@nestjs/common';
const CurrentUser = createRouterParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user; // From your auth middleware
}
);
@Router()
export class UserRouter {
@Route()
async getProfile(
{}, // ✅ First param still reserved for input (can be empty object)
@CurrentUser() user: User, // ✅ Custom decorator
) {
return user;
}
}