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: BuildProcedure<InputDef<RootDef<Context, {}>, z.ZodObject<{
            id: z.ZodString;
        }, z.core.$strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                user: User;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, z.ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}
appRouter
= {
post: {
    byId: BuildProcedure<InputDef<RootDef<Context, {}>, z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>, "query", {
        output: Post;
        errors: TrpcError<"NOT_FOUND", {
            id: string;
        }>;
        services: never;
        unmapped: never;
    }>;
    list: BuildProcedure<RootDef<Context, {}>, "query", {
        output: Post[];
        errors: never;
        services: never;
        unmapped: never;
    }>;
    create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
        ctx: Context;
        ctxAdded: {
            user: User;
        };
        inputOut: Unset;
        errors: TrpcError<...>;
        shorts: never;
    }>, z.ZodObject<...>>, "mutation", {
        ...;
    }>;
}
post
: {
byId: BuildProcedure<InputDef<RootDef<Context, {}>, z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>, "query", {
    output: Post;
    errors: TrpcError<"NOT_FOUND", {
        id: string;
    }>;
    services: never;
    unmapped: 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;
}>): BuildProcedure<InputDef<RootDef<Context, {}>, z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>, "query", {
    output: Post;
    errors: TrpcError<"NOT_FOUND", {
        id: string;
    }>;
    services: never;
    unmapped: 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: BuildProcedure<RootDef<Context, {}>, "query", {
    output: Post[];
    errors: never;
    services: never;
    unmapped: 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[]): BuildProcedure<RootDef<Context, {}>, "query", {
    output: Post[];
    errors: never;
    services: never;
    unmapped: 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: BuildProcedure<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>>, "mutation", {
    output: Post;
    errors: never;
    services: never;
    unmapped: 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): BuildProcedure<InputDef<With<RootDef<Context, {}>, {
    ctx: Context;
    ctxAdded: {
        user: User;
    };
    inputOut: Unset;
    errors: TrpcError<"UNAUTHORIZED", undefined>;
    shorts: never;
}>, z.ZodObject<...>>, "mutation", {
    ...;
}>
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: BuildProcedure<InputDef<RootDef<Context, {}>, z.ZodObject<{
            id: z.ZodString;
        }, z.core.$strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                user: User;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, z.ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}
AppRouter
= typeof
const appRouter: {
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, z.ZodObject<{
            id: z.ZodString;
        }, z.core.$strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                user: User;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, z.ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}
appRouter
;
export const const handler: FetchHandler<undefined>handler =
createFetchHandler<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, z.ZodObject<{
            id: z.ZodString;
        }, z.core.$strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                user: User;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, z.ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}, undefined>(opts: FetchHandlerOptions<...>): FetchHandler<...>
createFetchHandler
({
BaseHandlerOptions<{ post: { byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<...>; services: never; unmapped: never; }>; list: BuildProcedure<...>; create: BuildProcedure<...>; }; }>.router: {
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, z.ZodObject<{
            id: z.ZodString;
        }, z.core.$strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                user: User;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, z.ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}
router
:
const appRouter: {
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, z.ZodObject<{
            id: z.ZodString;
        }, z.core.$strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                user: User;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, z.ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}
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 rate limits, locks and durable calls.

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.
  • Contract-first: describe procedures in a contract and implement them later.
  • Server-side calls: call procedures in process, for tests and server code.

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