Decorator-first tRPC integration for NestJS with full Nest lifecycle support.
The documentation site under website/docs is the canonical source of truth for usage guides and support policy.
This repository contains:
packages/trpc: the nest-trpc-native integration packagesample/00-showcase: the full end-to-end sample appsample/01-* onward: focused runnable samples for single-topic onboardingnest-trpc-native makes tRPC feel native in Nest applications:
TrpcModule.forRoot() / TrpcModule.forRootAsync()@Router(), @Query(), @Mutation(), @Subscription()@Input() and @TrpcContext()The sample/ directory follows a scalable pattern:
sample/00-showcase: full production-style app with every major capabilitysample/01-* and onward: focused, low-cognitive-load samples by topicStart with sample/00-showcase for end-to-end behavior, then use sample/README.md and website/docs/samples/index.md to jump to specific features.
No download data available
No tracked packages depend on this.
For explicit monorepo and microservice transport topologies, see website/docs/advanced/monorepo.md and website/docs/advanced/microservices.md.
For explicit forRootAsync + ConfigService + middleware, run:
npm run test --workspace nest-trpc-native-sample-09-config-middleware>=2011.x11.xFor the supported surface and compatibility policy, see website/docs/support-policy.md.
npm i nest-trpc-native @trpc/server
Required peers:
npm i @nestjs/common @nestjs/core reflect-metadata rxjs
Optional (recommended for schema inference and tRPC-style validation, Zod v4):
npm i zod@^4
nest-trpc-native is intentionally a bridge package with an empty runtime dependency block ("dependencies": {}).
Why:
reflect-metadata)@nestjs/common, @nestjs/core)@trpc/server)fs, path)No. Zod is optional.
class-validator + ValidationPipe), you can use nest-trpc-native without Zod schemas.@Query({ input: z.object(...) }), @Mutation({ output: ... })) and schema generation based on those schemas, then your app should install Zod v4.We support Zod v4 as an optional peer dependency so non-Zod users are not forced to install it.
Enhancer lifecycle behavior is preserved through a narrow internal boundary so Nest-version-sensitive wiring stays isolated. See website/docs/advanced/nest-internals.md.
import { Module, UsePipes, ValidationPipe } from '@nestjs/common';
import { IsString, MinLength } from 'class-validator';
import { Input, Mutation, Query, Router, TrpcModule } from 'nest-trpc-native';
class CreateUserDto {
@IsString()
@MinLength(1)
name!: string;
}
@Router('users')
class UsersRouter {
@Query()
list() {
return [{ id: '1', name: 'Ada' }];
}
@Mutation()
@UsePipes(new ValidationPipe({ whitelist: true }))
create(@Input() input: CreateUserDto) {
return { id: '2', ...input };
}
}
@Module({
imports: [
TrpcModule.forRoot({
path: '/trpc',
autoSchemaFile: 'src/@generated/server.ts',
}),
],
providers: [UsersRouter],
})
export class AppModule {}
import { Module } from '@nestjs/common';
import {
Input,
Mutation,
Query,
Router,
TrpcContext,
TrpcModule,
} from 'nest-trpc-native';
import { z } from 'zod';
const CreateUserSchema = z.object({ name: z.string().min(1) });
@Router('users')
class UsersRouter {
@Query({ output: z.array(z.object({ id: z.string(), name: z.string() })) })
list(@TrpcContext('requestId') requestId: string) {
return [{ id: requestId, name: 'Ada' }];
}
@Mutation({ input: CreateUserSchema })
create(@Input() input: { name: string }) {
return { id: '1', ...input };
}
}
@Module({
imports: [
TrpcModule.forRoot({
path: '/trpc',
autoSchemaFile: 'src/@generated/server.ts',
}),
],
providers: [UsersRouter],
})
export class AppModule {}
Both TrpcModule.forRoot() and TrpcModule.forRootAsync() accept an optional generic that types the createContext return value. This gives you compile-time safety and autocompletion on your context factory without changing any runtime behavior.
interface MyContext {
requestId: string;
userId?: number;
}
// Static configuration
TrpcModule.forRoot<MyContext>({
createContext: ({ req }) => ({
requestId: req.headers['x-request-id'] ?? crypto.randomUUID(),
userId: extractUserId(req),
}),
});
// Async configuration
TrpcModule.forRootAsync<MyContext>({
useFactory: (config: ConfigService) => ({
createContext: ({ req }) => ({
requestId: req.headers['x-request-id'] ?? crypto.randomUUID(),
// TypeScript error if you forget `userId` or add unknown fields
}),
}),
inject: [ConfigService],
});
The generic defaults to any, so existing code without the type parameter continues to work unchanged.
npm install
npm run build
npm run test
npm run ci:sample
ci:showcase remains as a compatibility alias.
npm run start --workspace nest-trpc-native-showcase
npm run start:fastify --workspace nest-trpc-native-showcase
npm run client --workspace nest-trpc-native-showcase
npm run sample:focused
For sample details, open sample/README.md and sample/00-showcase/README.md.