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:
FileandBlobvalues in inputs and outputs. - Responses: headers, status, or a
Responseof your own. - Serializers: plain JSON by default, or types like
DateandMapthat 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.