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

The server

How procedures, routers, context and the handler fit together, and where each page of this section comes in.

On this page

A server is a few plain values. initTrpc() makes the root t from the context type, t.procedure builds procedures, a router is an object of procedures, and createFetchHandler serves the router over HTTP, SSE and WebSockets. This is the blog app that most code blocks in these docs build on:

server.ts
/**
 * The app most pages' code blocks build on: a blog's router, its handler
 * and its context. Blocks import it as `./server.ts` behind a cut.
 */
import { function createFetchHandler<TRouter extends AnyRouter, TPlatform = undefined>(opts: FetchHandlerOptions<TRouter, TPlatform>): FetchHandler<TPlatform>createFetchHandler, function error<const TCode extends string, TData = undefined>(opts: Omit<TrpcErrorOptions<TCode, TData>, "defined">): TrpcError<TCode, TData>error, function initTrpc<TConfig extends RootConfig = {}>(opts?: RootOptions<TConfig["meta"] extends object ? TConfig["meta"] : {}>): TrpcRoot<TConfig["ctx"] extends object ? TConfig["ctx"] : {}, TConfig["meta"] extends object ? TConfig["meta"] : {}>initTrpc } from '@trpc12/trpc/server';
import { import zz } from 'zod';

import { 
const db: {
    post: {
        find: (id: string) => Post | undefined;
        list: () => Post[];
        create: (input: {
            title: string;
            author?: string | null;
        }) => Post;
    };
}
db
, type User } from './db.ts';
export {
const db: {
    post: {
        find: (id: string) => Post | undefined;
        list: () => Post[];
        create: (input: {
            title: string;
            author?: string | null;
        }) => Post;
    };
}
export db
db
, type export PostPost, type export UserUser } from './db.ts';
export interface Context { Context.user: User | nulluser: User | null; } export const const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t =
initTrpc<{
    ctx: Context;
}>(opts?: RootOptions<{}>): TrpcRoot<Context, {}, RootDef<Context, {}>>
initTrpc
<{ ctx: Contextctx: Context }>();
export const
const authed: ProcedureBuilder<With<RootDef<Context, {}>, {
    ctx: Context;
    ctxAdded: {
        user: User;
    };
    inputOut: Unset;
    errors: TrpcError<"UNAUTHORIZED", undefined>;
    shorts: never;
}>>
authed
= const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure.
ProcedureBuilder<RootDef<Context, {}>>.use<{
    user: User;
}, TrpcError<"UNAUTHORIZED", undefined>, never, Unset, {}>(fn: MiddlewareFunction<{
    user: User | null;
}, undefined, {}, {
    user: User;
}, TrpcError<"UNAUTHORIZED", undefined>, never, Unset>): ProcedureBuilder<With<RootDef<Context, {}>, {
    ctx: Context;
    ctxAdded: {
        user: User;
    };
    inputOut: Unset;
    errors: TrpcError<"UNAUTHORIZED", undefined>;
    shorts: never;
}>> (+1 overload)
use
((
opts: MiddlewareOpts<{
    user: User | null;
}, undefined, {}>
opts
) =>
opts: MiddlewareOpts<{
    user: User | null;
}, undefined, {}>
opts
.
MiddlewareOpts<{ user: User | null; }, undefined, {}>.ctx: {
    user: User | null;
}
ctx
.user: User | nulluser
?
opts: MiddlewareOpts<{
    user: User | null;
}, undefined, {}>
opts
.
MiddlewareOpts<{ user: User | null; }, undefined, {}>.next: NextFn
<{
    user: User;
}>(opts: {
    ctx: {
        user: User;
    };
}) => Promise<MiddlewareResult<{
    user: User;
}, Unset>> (+2 overloads)
next
({
ctx: {
    user: User;
}
ctx
: { user: Useruser:
opts: MiddlewareOpts<{
    user: User | null;
}, undefined, {}>
opts
.
MiddlewareOpts<{ user: User | null; }, undefined, {}>.ctx: {
    user: User | null;
}
ctx
.user: Useruser } })
: error<"UNAUTHORIZED", undefined>(opts: Omit<TrpcErrorOptions<"UNAUTHORIZED", undefined>, "defined">): TrpcError<"UNAUTHORIZED", undefined>error({ code: "UNAUTHORIZED"code: 'UNAUTHORIZED' }), ); export const
const PostSchema: z.ZodObject<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}, z.core.$strip>
PostSchema
= import zz.
function object<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}>(shape?: {
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}, z.core.$strip>
object
({
id: z.ZodStringid: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(), title: z.ZodStringtitle: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(), author: z.ZodNullable<z.ZodString>author: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string().ZodType<any, any, $ZodStringInternals<string>>.nullable(): z.ZodNullable<z.ZodString>nullable(), createdAt: z.ZodDatecreatedAt: import zz.function date(params?: string | z.core.$ZodDateParams): z.ZodDatedate(), }); export const
const appRouter: {
    post: {
        byId: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: {
                id: string;
            };
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }> | SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            services: never;
        }>;
        list: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: undefined;
            output: Post[];
            errors: never;
            services: never;
        }>;
        create: Procedure<{
            type: "mutation";
            ctx: Context;
            meta: {};
            input: {
                title: string;
            };
            output: Post;
            errors: TrpcError<...> | SchemaValidationError<...>;
            services: never;
        }>;
    };
}
appRouter
= {
post: {
    byId: Procedure<{
        type: "query";
        ctx: Context;
        meta: {};
        input: {
            id: string;
        };
        output: Post;
        errors: TrpcError<"NOT_FOUND", {
            id: string;
        }> | SchemaValidationError<z.ZodObject<{
            id: z.ZodString;
        }, z.core.$strip>>;
        services: never;
    }>;
    list: Procedure<{
        type: "query";
        ctx: Context;
        meta: {};
        input: undefined;
        output: Post[];
        errors: never;
        services: never;
    }>;
    create: Procedure<{
        type: "mutation";
        ctx: Context;
        meta: {};
        input: {
            title: string;
        };
        output: Post;
        errors: TrpcError<...> | SchemaValidationError<...>;
        services: never;
    }>;
}
post
: {
byId: Procedure<{
    type: "query";
    ctx: Context;
    meta: {};
    input: {
        id: string;
    };
    output: Post;
    errors: TrpcError<"NOT_FOUND", {
        id: string;
    }> | SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    services: never;
}>
byId
: const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure
.
ProcedureBuilder<RootDef<Context, {}>>.input<z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<InputDef<RootDef<Context, {}>, z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>> (+1 overload)
input
(import zz.
function object<{
    id: z.ZodString;
}>(shape?: {
    id: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>
object
({ id: z.ZodStringid: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.
ProcedureBuilder<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>>.query<Post | TrpcError<"NOT_FOUND", {
    id: string;
}>>(resolver: (opts: ResolverOpts<InputDef<RootDef<Context, {}>, z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>>) => Post | TrpcError<"NOT_FOUND", {
    id: string;
}>): Procedure<{
    type: "query";
    ctx: Context;
    meta: {};
    input: {
        id: string;
    };
    output: Post;
    errors: TrpcError<"NOT_FOUND", {
        id: string;
    }> | SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    services: never;
}>
query
(
(
opts: ResolverOpts<InputDef<RootDef<Context, {}>, z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>>
opts
) =>
const db: {
    post: {
        find: (id: string) => Post | undefined;
        list: () => Post[];
        create: (input: {
            title: string;
            author?: string | null;
        }) => Post;
    };
}
db
.
post: {
    find: (id: string) => Post | undefined;
    list: () => Post[];
    create: (input: {
        title: string;
        author?: string | null;
    }) => Post;
}
post
.find: (id: string) => Post | undefinedfind(
opts: ResolverOpts<InputDef<RootDef<Context, {}>, z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>>
opts
.
ResolverOpts<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>>.input: {
    id: string;
}
input
.id: stringid) ??
error<"NOT_FOUND", {
    id: string;
}>(opts: Omit<TrpcErrorOptions<"NOT_FOUND", {
    id: string;
}>, "defined">): TrpcError<"NOT_FOUND", {
    id: string;
}>
error
({ code: "NOT_FOUND"code: 'NOT_FOUND',
data?: {
    id: string;
} | undefined
data
: { id: stringid:
opts: ResolverOpts<InputDef<RootDef<Context, {}>, z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>>
opts
.
ResolverOpts<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>>.input: {
    id: string;
}
input
.id: stringid } }),
),
list: Procedure<{
    type: "query";
    ctx: Context;
    meta: {};
    input: undefined;
    output: Post[];
    errors: never;
    services: never;
}>
list
: const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure.
ProcedureBuilder<RootDef<Context, {}>>.query<Post[]>(resolver: (opts: ResolverOpts<RootDef<Context, {}>>) => Post[]): Procedure<{
    type: "query";
    ctx: Context;
    meta: {};
    input: undefined;
    output: Post[];
    errors: never;
    services: never;
}>
query
(() =>
const db: {
    post: {
        find: (id: string) => Post | undefined;
        list: () => Post[];
        create: (input: {
            title: string;
            author?: string | null;
        }) => Post;
    };
}
db
.
post: {
    find: (id: string) => Post | undefined;
    list: () => Post[];
    create: (input: {
        title: string;
        author?: string | null;
    }) => Post;
}
post
.list: () => Post[]list()),
create: Procedure<{
    type: "mutation";
    ctx: Context;
    meta: {};
    input: {
        title: string;
    };
    output: Post;
    errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
        title: z.ZodString;
    }, z.core.$strip>>;
    services: never;
}>
create
:
const authed: ProcedureBuilder<With<RootDef<Context, {}>, {
    ctx: Context;
    ctxAdded: {
        user: User;
    };
    inputOut: Unset;
    errors: TrpcError<"UNAUTHORIZED", undefined>;
    shorts: never;
}>>
authed
.
ProcedureBuilder<With<RootDef<Context, {}>, { ctx: Context; ctxAdded: { user: User; }; inputOut: Unset; errors: TrpcError<"UNAUTHORIZED", undefined>; shorts: never; }>>.input<z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<InputDef<With<RootDef<Context, {}>, {
    ctx: Context;
    ctxAdded: {
        user: User;
    };
    inputOut: Unset;
    errors: TrpcError<"UNAUTHORIZED", undefined>;
    shorts: never;
}>, z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>>> (+1 overload)
input
(import zz.
function object<{
    title: z.ZodString;
}>(shape?: {
    title: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>
object
({ title: z.ZodStringtitle: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()._ZodString<$ZodStringInternals<string>>.min(minLength: number, params?: string | z.core.$ZodCheckMinLengthParams): z.ZodStringmin(1) }))
.
ProcedureBuilder<InputDef<With<RootDef<Context, {}>, { ctx: Context; ctxAdded: { user: User; }; inputOut: Unset; errors: TrpcError<"UNAUTHORIZED", undefined>; shorts: never; }>, ZodObject<...>>>.mutation<Post>(resolver: (opts: ResolverOpts<InputDef<With<RootDef<Context, {}>, {
    ctx: Context;
    ctxAdded: {
        user: User;
    };
    inputOut: Unset;
    errors: TrpcError<"UNAUTHORIZED", undefined>;
    shorts: never;
}>, z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>>>) => Post): Procedure<{
    type: "mutation";
    ctx: Context;
    meta: {};
    input: {
        title: string;
    };
    output: Post;
    errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<z.ZodObject<{
        title: z.ZodString;
    }, z.core.$strip>>;
    services: never;
}>
mutation
((
opts: ResolverOpts<InputDef<With<RootDef<Context, {}>, {
    ctx: Context;
    ctxAdded: {
        user: User;
    };
    inputOut: Unset;
    errors: TrpcError<"UNAUTHORIZED", undefined>;
    shorts: never;
}>, z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>>>
opts
) =>
const db: {
    post: {
        find: (id: string) => Post | undefined;
        list: () => Post[];
        create: (input: {
            title: string;
            author?: string | null;
        }) => Post;
    };
}
db
.
post: {
    find: (id: string) => Post | undefined;
    list: () => Post[];
    create: (input: {
        title: string;
        author?: string | null;
    }) => Post;
}
post
.
create: (input: {
    title: string;
    author?: string | null;
}) => Post
create
({ ...
opts: ResolverOpts<InputDef<With<RootDef<Context, {}>, {
    ctx: Context;
    ctxAdded: {
        user: User;
    };
    inputOut: Unset;
    errors: TrpcError<"UNAUTHORIZED", undefined>;
    shorts: never;
}>, z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>>>
opts
.
ResolverOpts<InputDef<With<RootDef<Context, {}>, { ctx: Context; ctxAdded: { user: User; }; inputOut: Unset; errors: TrpcError<"UNAUTHORIZED", undefined>; shorts: never; }>, ZodObject<...>>>.input: {
    title: string;
}
input
, author?: string | null | undefinedauthor:
opts: ResolverOpts<InputDef<With<RootDef<Context, {}>, {
    ctx: Context;
    ctxAdded: {
        user: User;
    };
    inputOut: Unset;
    errors: TrpcError<"UNAUTHORIZED", undefined>;
    shorts: never;
}>, z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>>>
opts
.
ResolverOpts<InputDef<With<RootDef<Context, {}>, { ctx: Context; ctxAdded: { user: User; }; inputOut: Unset; errors: TrpcError<"UNAUTHORIZED", undefined>; shorts: never; }>, ZodObject<...>>>.ctx: {
    user: User;
}
ctx
.user: Useruser.User.name: stringname }),
), }, }; export type
type AppRouter = {
    post: {
        byId: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: {
                id: string;
            };
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }> | SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            services: never;
        }>;
        list: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: undefined;
            output: Post[];
            errors: never;
            services: never;
        }>;
        create: Procedure<{
            type: "mutation";
            ctx: Context;
            meta: {};
            input: {
                title: string;
            };
            output: Post;
            errors: TrpcError<...> | SchemaValidationError<...>;
            services: never;
        }>;
    };
}
AppRouter
= typeof
const appRouter: {
    post: {
        byId: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: {
                id: string;
            };
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }> | SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            services: never;
        }>;
        list: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: undefined;
            output: Post[];
            errors: never;
            services: never;
        }>;
        create: Procedure<{
            type: "mutation";
            ctx: Context;
            meta: {};
            input: {
                title: string;
            };
            output: Post;
            errors: TrpcError<...> | SchemaValidationError<...>;
            services: never;
        }>;
    };
}
appRouter
;
export const const handler: FetchHandler<undefined>handler =
createFetchHandler<{
    post: {
        byId: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: {
                id: string;
            };
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }> | SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            services: never;
        }>;
        list: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: undefined;
            output: Post[];
            errors: never;
            services: never;
        }>;
        create: Procedure<{
            type: "mutation";
            ctx: Context;
            meta: {};
            input: {
                title: string;
            };
            output: Post;
            errors: TrpcError<...> | SchemaValidationError<...>;
            services: never;
        }>;
    };
}, undefined>(opts: FetchHandlerOptions<...>): FetchHandler<...>
createFetchHandler
({
BaseHandlerOptions<{ post: { byId: Procedure<{ type: "query"; ctx: Context; meta: {}; input: { id: string; }; output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }> | SchemaValidationError<ZodObject<{ id: ZodString; }, $strip>>; services: never; }>; list: Procedure<...>; create: Procedure<...>; }; }>.router: {
    post: {
        byId: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: {
                id: string;
            };
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }> | SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            services: never;
        }>;
        list: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: undefined;
            output: Post[];
            errors: never;
            services: never;
        }>;
        create: Procedure<{
            type: "mutation";
            ctx: Context;
            meta: {};
            input: {
                title: string;
            };
            output: Post;
            errors: TrpcError<...> | SchemaValidationError<...>;
            services: never;
        }>;
    };
}
router
:
const appRouter: {
    post: {
        byId: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: {
                id: string;
            };
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }> | SchemaValidationError<z.ZodObject<{
                id: z.ZodString;
            }, z.core.$strip>>;
            services: never;
        }>;
        list: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: undefined;
            output: Post[];
            errors: never;
            services: never;
        }>;
        create: Procedure<{
            type: "mutation";
            ctx: Context;
            meta: {};
            input: {
                title: string;
            };
            output: Post;
            errors: TrpcError<...> | SchemaValidationError<...>;
            services: never;
        }>;
    };
}
appRouter
,
createContext: (opts: CreateContextOpts<undefined>) => MaybePromise<{
    user: User | null;
}>
createContext
: () => ({ user: User | nulluser: null }),
});

Building procedures

  • Procedures: queries, mutations and subscriptions, with validated input and output.
  • Routers: objects of procedures, merged and split into lazy subtrees.
  • Context: what every call gets as ctx, from the request or from middleware.
  • Middleware: base procedures that add to the context, replace the input or stop a call. Built-in middleware covers lifecycle hooks.

What a procedure returns

  • Errors: one error class, typed on the client from what resolvers return.
  • Streaming: async generators, Effect streams and nested promises.
  • Files: File and Blob values in inputs and outputs.
  • Responses: headers, status, or a Response of your own.
  • Serializers: plain JSON by default, or types like Date and Map that survive the wire.

Growing an app

  • Effect: resolvers that return Effects, with services from the handler's layer.
  • Extensions: packages that add middleware, context, meta and errors to every procedure.
  • Server-side calls: call procedures in process, for tests and server code.

The handler runs on every runtime: Adapters has one page per runtime.