Define Procedures
On this page
A procedure is a function which is exposed to the client, it can be one of:
- a
Query- used to fetch data, generally does not change any data - a
Mutation- used to send data, often for create/update/delete purposes - a
Subscription- you might not need this, and we have dedicated documentation
Procedures in tRPC are very flexible primitives to create backend functions. They use an immutable builder pattern, which means you can create reusable base procedures that share functionality among multiple procedures.
Writing procedures
The t object you create during tRPC setup returns an initial t.procedure which all other procedures are built on:
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';
import { import zz } from 'zod';
const const t: TRPCRootObject<{
signGuestBook: () => Promise<void>;
}, object, RuntimeConfigOptions<{
signGuestBook: () => Promise<void>;
}, object>, {
ctx: {
signGuestBook: () => Promise<void>;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.context<{
signGuestBook: () => Promise<void>;
}>(): TRPCBuilder<{
signGuestBook: () => Promise<void>;
}, object>
context<{ signGuestBook: () => Promise<void>signGuestBook: () => interface Promise<T>Promise<void> }>().TRPCBuilder<{ signGuestBook: () => Promise<void>; }, object>.create<RuntimeConfigOptions<{
signGuestBook: () => Promise<void>;
}, object>>(opts?: RuntimeConfigOptions<{
signGuestBook: () => Promise<void>;
}, object> | undefined): TRPCRootObject<{
signGuestBook: () => Promise<void>;
}, object, RuntimeConfigOptions<{
signGuestBook: () => Promise<void>;
}, object>, {
ctx: {
signGuestBook: () => Promise<void>;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
export const const router: RouterBuilder<{
ctx: {
signGuestBook: () => Promise<void>;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
router = const t: TRPCRootObject<{
signGuestBook: () => Promise<void>;
}, object, RuntimeConfigOptions<{
signGuestBook: () => Promise<void>;
}, object>, {
ctx: {
signGuestBook: () => Promise<void>;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<{ signGuestBook: () => Promise<void>; }, object, RuntimeConfigOptions<{ signGuestBook: () => Promise<void>; }, object>, { ctx: { signGuestBook: () => Promise<void>; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder<{
ctx: {
signGuestBook: () => Promise<void>;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
router;
export const const publicProcedure: ProcedureBuilder<{
signGuestBook: () => Promise<void>;
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
publicProcedure = const t: TRPCRootObject<{
signGuestBook: () => Promise<void>;
}, object, RuntimeConfigOptions<{
signGuestBook: () => Promise<void>;
}, object>, {
ctx: {
signGuestBook: () => Promise<void>;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<{ signGuestBook: () => Promise<void>; }, object, RuntimeConfigOptions<{ signGuestBook: () => Promise<void>; }, object>, { ctx: { signGuestBook: () => Promise<void>; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{
signGuestBook: () => Promise<void>;
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedure;
const const appRouter: BuiltRouter<{
ctx: {
signGuestBook: () => Promise<void>;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
hello: QueryProcedure<{
input: void;
output: {
message: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
goodbye: MutationProcedure<{
input: void;
output: {
message: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
appRouter = const router: RouterBuilder
<{
hello: QueryProcedure<{
input: void;
output: {
message: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
goodbye: MutationProcedure<{
input: void;
output: {
message: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>(_: {
hello: QueryProcedure<{
input: void;
output: {
message: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
goodbye: MutationProcedure<{
input: void;
output: {
message: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}) => BuiltRouter<...>
router({
// Queries are the best place to fetch data
hello: QueryProcedure<{
input: void;
output: {
message: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
hello: const publicProcedure: ProcedureBuilder<{
signGuestBook: () => Promise<void>;
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
publicProcedure.ProcedureBuilder<{ signGuestBook: () => Promise<void>; }, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
message: string;
}>(resolver: ProcedureResolver<{
signGuestBook: () => Promise<void>;
}, object, object, UnsetMarker, UnsetMarker, {
message: string;
}>): QueryProcedure<{
input: void;
output: {
message: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
query(() => {
return {
message: stringmessage: 'hello world',
};
}),
// Mutations are the best place to do things like updating a database
goodbye: MutationProcedure<{
input: void;
output: {
message: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
goodbye: const publicProcedure: ProcedureBuilder<{
signGuestBook: () => Promise<void>;
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
publicProcedure.ProcedureBuilder<{ signGuestBook: () => Promise<void>; }, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.mutation<{
message: string;
}>(resolver: ProcedureResolver<{
signGuestBook: () => Promise<void>;
}, object, object, UnsetMarker, UnsetMarker, {
message: string;
}>): MutationProcedure<{
input: void;
output: {
message: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
mutation(async (opts: ProcedureResolverOptions<{
signGuestBook: () => Promise<void>;
}, object, object, UnsetMarker>
opts) => {
await opts: ProcedureResolverOptions<{
signGuestBook: () => Promise<void>;
}, object, object, UnsetMarker>
opts.ProcedureResolverOptions<{ signGuestBook: () => Promise<void>; }, object, object, UnsetMarker>.ctx: {
signGuestBook: () => Promise<void>;
}
ctx.signGuestBook: () => Promise<void>signGuestBook();
return {
message: stringmessage: 'goodbye!',
};
}),
});Reusable "Base Procedures"
As a general pattern we recommend you rename and export t.procedure as publicProcedure, which then makes room for you to create other named procedures for specific use cases and export those too. This pattern is called "base procedures" and is a key pattern for code and behaviour re-use in tRPC; every application is likely to need it.
In the below code, we're using reusable base procedures to build common use-cases for our app - we're making a reusable base procedures for logged in users (authedProcedure) & another base procedure that takes an organizationId and validates that a user is part of that organization.
This is a simplified example; in practice you may want to use some combination of Headers, Context, Middleware, and Metadata, to authenticate and authorize your users.
import { const initTRPC: TRPCBuilder<object, object>initTRPC, class TRPCErrorTRPCError } from '@trpc/server';
import { import zz } from 'zod';
type type Organization = {
id: string;
name: string;
}
Organization = {
id: stringid: string;
name: stringname: string;
};
type type Membership = {
role: "ADMIN" | "MEMBER";
Organization: Organization;
}
Membership = {
role: "ADMIN" | "MEMBER"role: 'ADMIN' | 'MEMBER';
type Organization: OrganizationOrganization: type Organization = {
id: string;
name: string;
}
Organization;
};
type type User = {
id: string;
memberships: Membership[];
}
User = {
id: stringid: string;
memberships: Membership[]memberships: type Membership = {
role: "ADMIN" | "MEMBER";
Organization: Organization;
}
Membership[];
};
type type Context = {
user: User | null;
}
Context = {
/**
* User is nullable
*/
user: User | nulluser: type User = {
id: string;
memberships: Membership[];
}
User | null;
};
const const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.context<Context>(): TRPCBuilder<Context, object>context<type Context = {
user: User | null;
}
Context>().TRPCBuilder<Context, object>.create<RuntimeConfigOptions<Context, object>>(opts?: RuntimeConfigOptions<Context, object> | undefined): TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
export const const publicProcedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure = const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure;
// procedure that asserts that the user is logged in
export const const authedProcedure: ProcedureBuilder<Context, object, {
user: User;
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
authedProcedure = const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure.ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
user: User;
}>(fn: MiddlewareBuilder<{
user: User | null;
}, object, {
user: User;
}, UnsetMarker> | MiddlewareFunction<Context, object, object, {
user: User;
}, UnsetMarker>): ProcedureBuilder<Context, object, {
user: User;
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(async function function (local function) isAuthed(opts: {
ctx: {
user: User | null;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}): Promise<...>
isAuthed(opts: {
ctx: {
user: User | null;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) {
const { const ctx: {
user: User | null;
}
ctx } = opts: {
ctx: {
user: User | null;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts;
// `ctx.user` is nullable
if (!const ctx: {
user: User | null;
}
ctx.user) { throw new new TRPCError(opts: {
message?: string;
code: TRPC_ERROR_CODE_KEY;
cause?: unknown;
}): TRPCError
TRPCError({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'UNAUTHORIZED' });
}
return opts: {
ctx: {
user: User | null;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
user: User;
}>(opts: {
ctx?: {
user: User;
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
user: User;
}>> (+2 overloads)
next({
ctx?: {
user: User;
} | undefined
ctx: {
// ✅ user value is known to be non-null now
user: Useruser: const ctx: {
user: User | null;
}
ctx.user: Useruser,
},
});
});
// procedure that asserts a user is a member of a specific organization
export const const organizationProcedure: ProcedureBuilder<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
}, {
organizationId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
organizationProcedure = const authedProcedure: ProcedureBuilder<Context, object, {
user: User;
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
authedProcedure
.ProcedureBuilder<Context, object, { user: User; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
organizationId: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
organizationId: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<Context, object, {
user: User;
}, {
organizationId: string;
}, {
organizationId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input(import zz.function object<{
organizationId: z.ZodString;
}>(shape?: {
organizationId: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
organizationId: z.ZodString;
}, z.core.$strip>
object({ organizationId: z.ZodStringorganizationId: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.ProcedureBuilder<Context, object, { user: User; }, { organizationId: string; }, { organizationId: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
Organization: Organization;
}>(fn: MiddlewareBuilder<{
user: User;
}, object, {
Organization: Organization;
}, {
organizationId: string;
}> | MiddlewareFunction<Context, object, {
user: User;
}, {
Organization: Organization;
}, {
organizationId: string;
}>): ProcedureBuilder<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
}, {
organizationId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(function function (local function) isMemberOfOrganization(opts: {
ctx: {
user: User;
};
type: ProcedureType;
path: string;
input: {
organizationId: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
user: User;
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}): Promise<...>
isMemberOfOrganization(opts: {
ctx: {
user: User;
};
type: ProcedureType;
path: string;
input: {
organizationId: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
user: User;
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) {
const const membership: Membership | undefinedmembership = opts: {
ctx: {
user: User;
};
type: ProcedureType;
path: string;
input: {
organizationId: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
user: User;
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.ctx: {
user: User;
}
ctx.user: Useruser.memberships: Membership[]memberships.Array<Membership>.find(predicate: (value: Membership, index: number, obj: Membership[]) => unknown, thisArg?: any): Membership | undefined (+1 overload)find(
(m: Membershipm) => m: Membershipm.type Organization: OrganizationOrganization.id: stringid === opts: {
ctx: {
user: User;
};
type: ProcedureType;
path: string;
input: {
organizationId: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
user: User;
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.input: {
organizationId: string;
}
input.organizationId: stringorganizationId,
);
if (!const membership: Membership | undefinedmembership) {
throw new new TRPCError(opts: {
message?: string;
code: TRPC_ERROR_CODE_KEY;
cause?: unknown;
}): TRPCError
TRPCError({
code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'FORBIDDEN',
});
}
return opts: {
ctx: {
user: User;
};
type: ProcedureType;
path: string;
input: {
organizationId: string;
};
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
user: User;
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
Organization: Organization;
}>(opts: {
ctx?: {
Organization: Organization;
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
Organization: Organization;
}>> (+2 overloads)
next({
ctx?: {
Organization: Organization;
} | undefined
ctx: {
type Organization: OrganizationOrganization: const membership: Membershipmembership.type Organization: OrganizationOrganization,
},
});
});
export const const appRouter: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
whoami: QueryProcedure<{
input: void;
output: User;
meta: object;
errorShape: DefaultErrorShape;
}>;
addMember: MutationProcedure<{
input: {
organizationId: string;
email: string;
};
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
appRouter = const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
whoami: QueryProcedure<{
input: void;
output: User;
meta: object;
errorShape: DefaultErrorShape;
}>;
addMember: MutationProcedure<{
input: {
organizationId: string;
email: string;
};
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>(_: {
whoami: QueryProcedure<{
input: void;
output: User;
meta: object;
errorShape: DefaultErrorShape;
}>;
addMember: MutationProcedure<{
input: {
organizationId: string;
email: string;
};
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
}) => BuiltRouter<...>
router({
whoami: QueryProcedure<{
input: void;
output: User;
meta: object;
errorShape: DefaultErrorShape;
}>
whoami: const authedProcedure: ProcedureBuilder<Context, object, {
user: User;
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
authedProcedure.ProcedureBuilder<Context, object, { user: User; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<User>(resolver: ProcedureResolver<Context, object, {
user: User;
}, UnsetMarker, UnsetMarker, User>): QueryProcedure<{
input: void;
output: User;
meta: object;
errorShape: DefaultErrorShape;
}>
query(async (opts: ProcedureResolverOptions<Context, object, {
user: User;
}, UnsetMarker>
opts) => {
// user is non-nullable here
const { ctx } = opts: ProcedureResolverOptions<Context, object, {
user: User;
}, UnsetMarker>
opts; return const ctx: {
user: User;
}
ctx.user: Useruser;
}),
addMember: MutationProcedure<{
input: {
organizationId: string;
email: string;
};
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
addMember: const organizationProcedure: ProcedureBuilder<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
}, {
organizationId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
organizationProcedure
.ProcedureBuilder<Context, object, { user: User; Organization: Organization; }, { organizationId: string; }, { organizationId: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
email: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
email: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
email: string;
}, {
organizationId: string;
email: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input(
import zz.function object<{
email: z.ZodString;
}>(shape?: {
email: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
email: z.ZodString;
}, z.core.$strip>
object({
email: z.ZodStringemail: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string().ZodString.email(params?: string | z.core.$ZodCheckEmailParams): z.ZodStringemail(),
}),
)
.ProcedureBuilder<Context, object, { user: User; Organization: Organization; }, { organizationId: string; email: string; }, { organizationId: string; email: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.mutation<string>(resolver: ProcedureResolver<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
email: string;
}, UnsetMarker, string>): MutationProcedure<{
input: {
organizationId: string;
email: string;
};
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
mutation((opts: ProcedureResolverOptions<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
email: string;
}>
opts) => {
// ctx contains the non-nullable user & the organization being queried
const { ctx } = opts: ProcedureResolverOptions<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
email: string;
}>
opts;
// input includes the validated email of the user being invited & the validated organizationId
const { input } = opts: ProcedureResolverOptions<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
email: string;
}>
opts;
return '...';
}),
});Inferring the options type of a "Base Procedure"
In addition to being able to infer the input and output types of a procedure, you can also infer the options type of a specific procedure builder (or base procedure) using inferProcedureBuilderResolverOptions.
This type helper is useful for declaring a type to a function's parameters. Like for example, separating the procedure's handler (main execution code) from its definition at the router, or for creating a helper function that works with multiple procedures.
async function function getMembersOfOrganization(opts: inferProcedureBuilderResolverOptions<typeof organizationProcedure>): Promise<any>getMembersOfOrganization(
opts: ProcedureResolverOptions<Context, object, {
user: User;
Organization: Organization;
}, {
[x: string]: unknown;
organizationId: string;
}>
opts: type inferProcedureBuilderResolverOptions<TProcedureBuilder extends AnyProcedureBuilder> = TProcedureBuilder extends ProcedureBuilder<infer TContext, infer TMeta, infer TContextOverrides, infer _TInputIn, infer TInputOut, infer _TOutputIn, infer _TOutputOut, infer _TCaller extends boolean, infer _TErrorShape> ? ProcedureResolverOptions<TContext, TMeta, TContextOverrides, TInputOut extends UnsetMarker ? unknown : TInputOut extends object ? Simplify<TInputOut & {
[keyAddedByInputCallFurtherDown: string]: unknown;
}> : TInputOut> : never
inferProcedureBuilderResolverOptions<typeof const organizationProcedure: ProcedureBuilder<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
}, {
organizationId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
organizationProcedure>,
) {
// input and ctx are now correctly typed!
const { const ctx: {
user: User;
Organization: Organization;
}
ctx, const input: {
[x: string]: unknown;
organizationId: string;
}
input } = opts: ProcedureResolverOptions<Context, object, {
user: User;
Organization: Organization;
}, {
[x: string]: unknown;
organizationId: string;
}>
opts;
return await let prisma: anyprisma.user.findMany({
where: {
membership: {
organizationId: string;
};
}
where: {
membership: {
organizationId: string;
}
membership: {
organizationId: stringorganizationId: const ctx: {
user: User;
Organization: Organization;
}
ctx.type Organization: OrganizationOrganization.id: stringid,
},
},
});
}
export const const appRouter: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
listMembers: QueryProcedure<{
input: {
organizationId: string;
};
output: any;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
appRouter = const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
listMembers: QueryProcedure<{
input: {
organizationId: string;
};
output: any;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>(_: {
listMembers: QueryProcedure<{
input: {
organizationId: string;
};
output: any;
meta: object;
errorShape: DefaultErrorShape;
}>;
}) => BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
listMembers: QueryProcedure<{
input: {
organizationId: string;
};
output: any;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
router({
listMembers: QueryProcedure<{
input: {
organizationId: string;
};
output: any;
meta: object;
errorShape: DefaultErrorShape;
}>
listMembers: const organizationProcedure: ProcedureBuilder<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
}, {
organizationId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
organizationProcedure.ProcedureBuilder<Context, object, { user: User; Organization: Organization; }, { organizationId: string; }, { organizationId: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<any>(resolver: ProcedureResolver<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
}, UnsetMarker, any>): QueryProcedure<{
input: {
organizationId: string;
};
output: any;
meta: object;
errorShape: DefaultErrorShape;
}>
query(async (opts: ProcedureResolverOptions<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
}>
opts) => {
// use helper function!
const const members: anymembers = await function getMembersOfOrganization(opts: inferProcedureBuilderResolverOptions<typeof organizationProcedure>): Promise<any>getMembersOfOrganization(opts: ProcedureResolverOptions<Context, object, {
user: User;
Organization: Organization;
}, {
organizationId: string;
}>
opts);
return const members: anymembers;
}),
});Subscriptions
For information on subscriptions, see our subscriptions guide.