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 HttpException → TRPCError 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.