Skip to content

tRPC v12 isn’t on npm yet. The @trpc12/* packages on this site can’t be installed until it’s released. Use v11 today

tRPC

ExperimentalContracts and OpenAPI might change or go away in any release. Stability

NestJS

Serve a contract from Nest controllers with @trpc12/experimental-nest.

On this page

@trpc12/experimental-nest serves a contract from Nest controllers. @Implement(contract) adds the Nest route for the contract's REST mapping; the method returns the procedure, so the controller's injected services reach the resolver through this. The whole package is experimental, as its name says, so its exports have no prefix: it might change or go away in any release.

Not on npm yet
pnpm add @trpc12/trpc effect @trpc12/experimental-nest @nestjs/common @nestjs/core @effect/platform-node rxjs
npm install @trpc12/trpc effect @trpc12/experimental-nest @nestjs/common @nestjs/core @effect/platform-node rxjs
yarn add @trpc12/trpc effect @trpc12/experimental-nest @nestjs/common @nestjs/core @effect/platform-node rxjs
bun add @trpc12/trpc effect @trpc12/experimental-nest @nestjs/common @nestjs/core @effect/platform-node rxjs
import { function Implement(contract: object): (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => voidImplement, class TrpcModuleTrpcModule } from '@trpc12/experimental-nest';

const 
const impl: ImplementResult<ExtendedDef<RootDef<{
    user: User | null;
}, {}>, RootDef<{}, OpenApiMeta>>, {
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
            meta: {
                route?: Route | undefined;
            };
        }>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                ...;
            };
            requires: never;
        }>, {
            ...;
        }>>;
    };
}>
impl
=
const t: TrpcRoot<{
    user: User | null;
}, {
    route?: Route | undefined;
}, ExtendedDef<RootDef<{
    user: User | null;
}, {}>, RootDef<{}, OpenApiMeta>>>
t
.
TrpcRoot<{ user: User | null; }, { route?: Route | undefined; }, ExtendedDef<RootDef<{ user: User | null; }, {}>, RootDef<{}, OpenApiMeta>>>.implement<{
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
            meta: {
                route?: Route | undefined;
            };
        }>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            requires: never;
        }>, {
            ...;
        }>>;
    };
}>(contract: {
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
            meta: {
                route?: Route | undefined;
            };
        }>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            requires: never;
        }>, {
            ...;
        }>>;
    };
}): ImplementResult<...>
implement
(
const appContract: {
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
            meta: {
                route?: Route | undefined;
            };
        }>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                ...;
            };
            requires: never;
        }>, {
            ...;
        }>>;
    };
}
appContract
);
@function Controller(): ClassDecorator (+2 overloads)Controller() class class PostControllerPostController { constructor(@function Inject(token?: InjectionToken | ForwardReference): PropertyDecorator & ParameterDecoratorInject(class PostsPosts) private readonly PostController.posts: Postsposts: class PostsPosts) {} @function Implement(contract: object): (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => voidImplement(
const appContract: {
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
            meta: {
                route?: Route | undefined;
            };
        }>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            requires: never;
        }>, {
            ...;
        }>>;
    };
}
appContract
.
post: {
    byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
        meta: {
            route?: Route | undefined;
        };
    }>, {
        inputIn: {
            id: string;
        };
        inputOut: {
            id: string;
        };
        errors: SchemaValidationError<z.ZodObject<{
            id: z.ZodString;
        }, z.core.$strip>>;
        requires: never;
    }>, {
        outputIn: {
            id: string;
            title: string;
            author: string | null;
            createdAt: Date;
        };
        outputOut: {
            id: string;
            title: string;
            author: string | null;
            createdAt: Date;
        };
        requires: never;
    }>, {
        ...;
    }>>;
}
post
.
byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        id: string;
    };
    errors: SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    outputOut: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    requires: never;
}>, {
    declared: {
        ...;
    };
    errors: SchemaValidationError<...> | TrpcError<...>;
}>>
byId
) // .meta({ route: { method: 'GET', path: '/posts/{id}' } })
PostController.byId(): ImplementedProcedure<ImplementerDef<ExtendedDef<RootDef<{
    user: User | null;
}, {}>, RootDef<{}, OpenApiMeta>>, ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        id: string;
    };
    errors: SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    outputOut: {
        ...;
    };
    requires: never;
}>, {
    ...;
}>>, ContractProcedure<...>, never>
byId
() {
return
const impl: ImplementResult<ExtendedDef<RootDef<{
    user: User | null;
}, {}>, RootDef<{}, OpenApiMeta>>, {
    post: {
        byId: ContractProcedure<"query", ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
            meta: {
                route?: Route | undefined;
            };
        }>, {
            inputIn: {
                id: string;
            };
            inputOut: {
                id: string;
            };
            errors: SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            requires: never;
        }>, {
            outputIn: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            outputOut: {
                id: string;
                title: string;
                author: string | null;
                createdAt: Date;
            };
            requires: never;
        }>, {
            ...;
        }>>;
    };
}>
impl
.
post: {
    readonly byId: ProcedureImplementer<ImplementerDef<ExtendedDef<RootDef<{
        user: User | null;
    }, {}>, RootDef<{}, OpenApiMeta>>, ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
        meta: {
            route?: Route | undefined;
        };
    }>, {
        inputIn: {
            id: string;
        };
        inputOut: {
            id: string;
        };
        errors: SchemaValidationError<z.ZodObject<{
            id: z.ZodString;
        }, z.core.$strip>>;
        requires: never;
    }>, {
        outputIn: {
            id: string;
            title: string;
            author: string | null;
            createdAt: Date;
        };
        outputOut: {
            id: string;
            title: string;
            author: string | null;
            createdAt: Date;
        };
        requires: never;
    }>, {
        ...;
    }>>, ContractProcedure<...>>;
}
post
.
byId: ProcedureImplementer<ImplementerDef<ExtendedDef<RootDef<{
    user: User | null;
}, {}>, RootDef<{}, OpenApiMeta>>, ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        id: string;
    };
    errors: SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    outputOut: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    requires: never;
}>, {
    declared: {
        ...;
    };
    errors: SchemaValidationError<...> | TrpcError<...>;
}>>, ContractProcedure<...>>
byId
.
query: <TrpcError<"NOT_FOUND", {
    id: string;
}> | Post>(resolver: (opts: ResolverOpts<ImplementerDef<ExtendedDef<RootDef<{
    user: User | null;
}, {}>, RootDef<{}, OpenApiMeta>>, ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        id: string;
    };
    errors: SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    outputOut: {
        ...;
    };
    requires: never;
}>, {
    ...;
}>>>) => (TrpcError<...> | Post) & (AnyTrpcError | {
    ...;
})) => ImplementedProcedure<...>
query
(
(
opts: ResolverOpts<ImplementerDef<ExtendedDef<RootDef<{
    user: User | null;
}, {}>, RootDef<{}, OpenApiMeta>>, ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        id: string;
    };
    errors: SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    outputOut: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    requires: never;
}>, {
    declared: {
        ...;
    };
    errors: SchemaValidationError<...> | TrpcError<...>;
}>>>
opts
) =>
this.PostController.posts: Postsposts.Posts.find(id: string): Post | undefinedfind(
opts: ResolverOpts<ImplementerDef<ExtendedDef<RootDef<{
    user: User | null;
}, {}>, RootDef<{}, OpenApiMeta>>, ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        id: string;
    };
    errors: SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    outputOut: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    requires: never;
}>, {
    declared: {
        ...;
    };
    errors: SchemaValidationError<...> | TrpcError<...>;
}>>>
opts
.
ResolverOpts<ImplementerDef<ExtendedDef<RootDef<{ user: User | null; }, {}>, RootDef<{}, OpenApiMeta>>, ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, { ...; }>, { ...; }>, { ...; }>, { ...; }>>>.input: {
    id: string;
}
input
.id: stringid) ??
opts: ResolverOpts<ImplementerDef<ExtendedDef<RootDef<{
    user: User | null;
}, {}>, RootDef<{}, OpenApiMeta>>, ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        id: string;
    };
    errors: SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    outputOut: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    requires: never;
}>, {
    declared: {
        ...;
    };
    errors: SchemaValidationError<...> | TrpcError<...>;
}>>>
opts
.
ResolverOpts<ImplementerDef<ExtendedDef<RootDef<{ user: User | null; }, {}>, RootDef<{}, OpenApiMeta>>, ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, { ...; }>, { ...; }>, { ...; }>, { ...; }>>>.errors: ErrorConstructors<{
    NOT_FOUND: TrpcError<"NOT_FOUND", {
        id: string;
    }>;
}>
errors
.
type NOT_FOUND: (opts: ErrorConstructorOpts<{
    id: string;
}>) => TrpcError<"NOT_FOUND", {
    id: string;
}>
NOT_FOUND
({
data: {
    id: string;
}
data
: { id: stringid:
opts: ResolverOpts<ImplementerDef<ExtendedDef<RootDef<{
    user: User | null;
}, {}>, RootDef<{}, OpenApiMeta>>, ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, {
    meta: {
        route?: Route | undefined;
    };
}>, {
    inputIn: {
        id: string;
    };
    inputOut: {
        id: string;
    };
    errors: SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    requires: never;
}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    outputOut: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    };
    requires: never;
}>, {
    declared: {
        ...;
    };
    errors: SchemaValidationError<...> | TrpcError<...>;
}>>>
opts
.
ResolverOpts<ImplementerDef<ExtendedDef<RootDef<{ user: User | null; }, {}>, RootDef<{}, OpenApiMeta>>, ContractWith<ContractWith<ContractWith<ContractWith<ContractRootDef<{}>, { ...; }>, { ...; }>, { ...; }>, { ...; }>>>.input: {
    id: string;
}
input
.id: stringid } }),
); } } @function Module(metadata: ModuleMetadata): ClassDecoratorModule({ ModuleMetadata.imports?: (Type<any> | ForwardReference<any> | DynamicModule | Promise<DynamicModule>)[] | undefinedimports: [ class TrpcModuleTrpcModule.TrpcModule.forRoot(options?: TrpcModuleOptions): DynamicModuleforRoot({ TrpcModuleOptions.createContext?: ((opts: CreateContextOpts<ExecutionContext>) => object | Promise<object>) | undefinedcreateContext: (opts: CreateContextOpts<ExecutionContext>opts) => ({ user: anyuser: opts: CreateContextOpts<ExecutionContext>opts.CreateContextOpts<ExecutionContext>.platform: ExecutionContextplatform.ArgumentsHost.switchToHttp(): HttpArgumentsHostswitchToHttp().HttpArgumentsHost.getRequest<any>(): anygetRequest().user, }), TrpcModuleOptions.layer?: Layer.Layer<any, any, never> | undefinedlayer: class DbDb.Db.layer: Layer.Layer<Db, never, never>layer, }), ], ModuleMetadata.controllers?: Type<any>[] | undefinedcontrollers: [class PostControllerPostController], ModuleMetadata.providers?: Provider[] | undefinedproviders: [class PostsPosts], }) class class AppModuleAppModule {}
  • A leaf needs a route path, .meta({ route: { path } }). Pass a contract router instead and return impl.router({ … }) (or a plain object of procedures) to get default paths from the keys below it (/post/byId).
  • TrpcModule.forRoot() takes the OpenAPI handler's options; createContext's platform is Nest's ExecutionContext. forRootAsync({ inject, useFactory }) builds them from providers. One handler serves every route, so the layer is built once and released on shutdown (app.enableShutdownHooks() or app.close()).
  • Guards, pipes and filters run as usual; put @Implement above the method's other decorators so they apply to every route it adds. The procedure's own middleware and validation run after them, and errors use the REST shape above.
  • Bodies Nest's JSON and urlencoded parsers read are used as parsed; others are read from the request.
  • Express only. Nest needs experimentalDecorators.

Routes and errors follow the OpenAPI handler.

The examples/experimental/nest app implements a contract in a controller, with a guard, an injected service and an Effect layer, and calls it with experimental_openApiLink.

Credits

@trpc12/experimental-nest follows oRPC's NestJS integration.