Skip to main content

TrpcModule.forRoot()

The simplest way to register the tRPC integration. Use this when your configuration is static and doesn't depend on injected providers.

Basic Usage

import { Module } from '@nestjs/common';
import { TrpcModule } from '@nest-native/trpc';

@Module({
imports: [
TrpcModule.forRoot({
path: '/trpc',
}),
],
})
export class AppModule {}

Options

OptionTypeDescription
pathstringThe HTTP path where tRPC procedures are served (e.g. '/trpc')
autoSchemaFilestringPath for auto-generated AppRouter type file
createContext(opts) => TContextFactory function to create tRPC context per request
transformerDataTransformertRPC data transformer (e.g. superjson), forwarded to initTRPC.create({ transformer })
errorFormatterTRPCErrorFormatterCustom error shape, forwarded to initTRPC.create({ errorFormatter })
responseMetaResponseMetaFnPer-response HTTP status/headers hook (e.g. Cache-Control), forwarded to the tRPC request handler
onErrorHTTPErrorHandlerCentralized error logging/reporting hook, forwarded to the tRPC request handler

With Schema Generation

TrpcModule.forRoot({
path: '/trpc',
autoSchemaFile: 'src/@generated/server.ts',
});

This generates a TypeScript file exporting your AppRouter type, which clients import for end-to-end type safety.

With Context

TrpcModule.forRoot({
path: '/trpc',
autoSchemaFile: 'src/@generated/server.ts',
createContext: ({ req }) => ({
requestId: req.headers['x-request-id'] ?? crypto.randomUUID(),
}),
});

See Typed Context for compile-time type safety on the context factory.

With a Data Transformer

Pass a tRPC data transformer such as superjson so values like Date, Map, and Set round-trip between server and client:

import superjson from 'superjson';

TrpcModule.forRoot({
path: '/trpc',
autoSchemaFile: 'src/@generated/server.ts',
transformer: superjson,
});

The client must configure the same transformer on its terminating link:

import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import superjson from 'superjson';
import type { AppRouter } from './@generated/server';

const client = createTRPCProxyClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:3000/trpc', transformer: superjson })],
});

When transformer and autoSchemaFile are combined, the generated AppRouter type is marked transformer-enabled, so typed clients get a compile-time error if they forget the link transformer, and transformed outputs such as Date are inferred as Date rather than string.

With an Error Formatter

Use errorFormatter to reshape the error payload sent to clients. It runs after the built-in HttpExceptionTRPCError mapping, so error.code is already the mapped tRPC code. The canonical recipe exposes flattened Zod issues:

import { z, ZodError } from 'zod';

TrpcModule.forRoot({
path: '/trpc',
errorFormatter: ({ shape, error }) => ({
...shape,
data: {
...shape.data,
zodError:
error.code === 'BAD_REQUEST' && error.cause instanceof ZodError
? z.flattenError(error.cause)
: null,
},
}),
});

Clients then read error.data.zodError.fieldErrors to render per-field messages.

With Response Meta and Error Reporting

responseMeta sets per-response HTTP status/headers (the standard place for Cache-Control), and onError is the centralized hook for logging and reporting failures:

TrpcModule.forRoot({
path: '/trpc',
responseMeta: ({ type, errors, eagerGeneration }) => ({
headers:
type === 'query' && errors.length === 0 && !eagerGeneration
? { 'cache-control': 'public, max-age=60' }
: {},
}),
onError: ({ error, path }) => {
console.error(`tRPC error on "${path}": ${error.message}`);
},
});

responseMeta headers are applied to both JSON responses and streamed SSE subscription responses. For SSE, the hook runs eagerly (eagerGeneration: true) before the first chunk is written, so a returned status cannot rewrite an already-streaming response.

All four options are forwarded to tRPC untouched and add no runtime dependencies to the package. See sample/13-transformer-error-formatting for a runnable demonstration.