Does NestJS Work With tRPC?

Partially CompatibleLast verified: 2026-02-20

You can use tRPC with NestJS, but they solve overlapping problems and require deliberate architectural choices to avoid redundancy.

Quick Facts

Compatibility
partial
Setup Difficulty
Moderate
Official Integration
No — community maintained
Confidence
medium
Minimum Versions
NestJS: 9.0.0
tRPC: 10.0.0

How NestJS Works With tRPC

NestJS and tRPC can coexist, but their design philosophies create tension. NestJS is a full framework with dependency injection, decorators, and built-in validation, while tRPC is a lightweight RPC library focused on type safety without schema files. The typical approach is to use NestJS as your HTTP server/DI container and expose tRPC routers as endpoints, or use NestJS controllers alongside separate tRPC procedures. However, this creates duplication: you're managing request validation, error handling, and middleware in both systems. A cleaner integration uses tRPC purely for client-side type safety, bypassing NestJS controllers entirely for that API surface. Developers often find themselves choosing between frameworks—using only NestJS's native capabilities or migrating entirely to tRPC with a simpler Node backend. If you need NestJS's ecosystem (guards, interceptors, dependency injection), tRPC adds minimal value. If you want tRPC's zero-codegen DX, NestJS's opinionated structure feels heavyweight.

Best Use Cases

Migrating a NestJS monolith to tRPC gradually by exposing legacy endpoints through tRPC adapters
Building real-time APIs where NestJS handles WebSockets and tRPC handles RPC procedures with shared type definitions
Full-stack applications where you want NestJS's advanced features (guards, interceptors) but need tRPC's client-side type safety
Microservices where NestJS services communicate internally via tRPC for type-safe inter-service calls

Quick Setup

bash
npm install @nestjs/core @nestjs/common @trpc/server zod
typescript
import { Controller, Post, Body } from '@nestjs/common';
import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.create();

const appRouter = t.router({
  greet: t.procedure
    .input(z.object({ name: z.string() }))
    .query(({ input }) => ({ message: `Hello ${input.name}` })),
});

export type AppRouter = typeof appRouter;

@Controller('trpc')
export class TrpcController {
  @Post('greet')
  async handleTrpc(@Body() body: { name: string }) {
    const caller = appRouter.createCaller({});
    return caller.greet({ name: body.name });
  }
}

Known Issues & Gotchas

warning

Duplicate validation logic between NestJS decorators (class-validator) and tRPC zod/yup schemas

Fix: Choose one validation system. Use tRPC's Zod validators throughout and access them in NestJS via middleware, or use NestJS pipes exclusively and skip tRPC validation.

warning

Conflicting middleware/interceptor patterns make request lifecycle unclear

Fix: Establish a clear boundary: use NestJS interceptors for HTTP concerns (logging, error formatting) and tRPC middleware for procedure-level logic.

warning

tRPC's built-in error handling doesn't integrate with NestJS exception filters

Fix: Create a custom tRPC error formatter that aligns with your NestJS error response shape, or route tRPC errors through NestJS exception handling.

info

Adding tRPC to an existing NestJS project increases bundle size and maintenance surface area without clear ROI

Fix: Evaluate if you need tRPC's features; NestJS 9+ with Swagger/OpenAPI provides similar DX for type generation.

Alternatives

  • NestJS + OpenAPI/Swagger: Use NestJS exclusively with Swagger for API documentation and type generation without adding tRPC.
  • Express + tRPC: Skip NestJS entirely and use Express for simpler routing, letting tRPC handle all RPC logic and type safety.
  • Fastify + tRPC: Use Fastify's lightweight HTTP server directly with tRPC for maximum performance and minimal framework overhead.

Resources

Related Compatibility Guides

Explore more compatibility guides