Move Fast and Break Nothing.End-to-end typesafe APIs made easy.
Experience the full power of TypeScript inference: write procedures on the server and call them from the client with their types. No schemas to generate, no code to sync. v12 is rebuilt on Effect, and you don't have to use it.
$ pnpm add @trpc12/trpc zod
import { function createTrpcClient<TSource, const TLinks extends ReadonlyArray<TrpcLink<any, RouterOf<TSource>>>>(opts: TypedClientOptions<TSource, TLinks> & DuplicateSwitchCheck<TLinks> & UnmetNeedCheck<TLinks>): TrpcClient<RouterOf<TSource>, LinksContext<TLinks>, undefined, LinksTypes<TLinks>>createTrpcClient, function httpLink<TOpts extends HttpLinkOptions>(opts: TOpts): TrpcLink<HttpLinkDecl<TOpts>>httpLink, function routerType<TRouter>(): RouterType<TRouter>routerType } from '@trpc12/trpc/client';
import type { type AppRouter = {
post: {
byId: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
title: ZodString;
}, $strip>>, "mutation", {
output: Post;
errors: never;
services: never;
unmapped: never;
}>;
};
}
AppRouter } from './server.ts';
const const client: TrpcClient<{
post: {
byId: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
title: ZodString;
}, $strip>>, "mutation", {
output: Post;
errors: never;
services: never;
unmapped: never;
}>;
};
}, {}, undefined, LinksTypes<...>>
client = createTrpcClient<RouterType<{
post: {
byId: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
title: ZodString;
}, $strip>>, "mutation", {
output: Post;
errors: never;
services: never;
unmapped: never;
}>;
};
}>, readonly [...]>(opts: TypedClientOptions<...>): TrpcClient<...>
createTrpcClient({
TypedClientOptions<RouterType<{ post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { ...; }>; create: BuildProcedure<...>; }; }>, readonly [...]>.router: RouterType<{
post: {
byId: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
title: ZodString;
}, $strip>>, "mutation", {
output: Post;
errors: never;
services: never;
unmapped: never;
}>;
};
}>
router: routerType<{
post: {
byId: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
title: ZodString;
}, $strip>>, "mutation", {
output: Post;
errors: never;
services: never;
unmapped: never;
}>;
};
}>(): RouterType<...>
routerType<type AppRouter = {
post: {
byId: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
title: ZodString;
}, $strip>>, "mutation", {
output: Post;
errors: never;
services: never;
unmapped: never;
}>;
};
}
AppRouter>(),
TypedClientOptions<RouterType<{ post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { ...; }>; create: BuildProcedure<...>; }; }>, readonly [...]>.links: readonly [TrpcLink<{
form: JsonForm;
provides: "response";
}, any>]
links: [httpLink<{
url: string;
}>(opts: {
url: string;
}): TrpcLink<{
form: JsonForm;
provides: "response";
}, any>
httpLink({ url: stringurl: '/trpc' })],
});
const post = await const client: TrpcClient<{
post: {
byId: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
title: ZodString;
}, $strip>>, "mutation", {
output: Post;
errors: never;
services: never;
unmapped: never;
}>;
};
}, {}, undefined, LinksTypes<...>>
client.post: ClientRouter<{
byId: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<RootDef<{
userId: string | null;
}, {}>, ZodObject<{
title: ZodString;
}, $strip>>, "mutation", {
output: Post;
errors: never;
services: never;
unmapped: never;
}>;
}, {}, undefined, LinksTypes<...>>
post.byId: {
query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
}, CallError<{
type: "query";
ctx: {
userId: string | null;
};
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | InputValidationError<{
id: string;
}>;
services: never;
}, JsonForm, never>>;
}
byId.function query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
}, CallError<{
type: "query";
ctx: {
userId: string | null;
};
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | InputValidationError<{
id: string;
}>;
services: never;
}, JsonForm, never>>
query({ id: stringid: '1' });The client of the router below: the types come from the server, with nothing generated.
How it works
Three files' worth of code, compiled when this site builds: the types you hover are the ones your editor shows.
Define procedures
ProceduresA router is a plain object of procedures. Each one validates its input with any Standard Schema library and returns its output, or an error with typed data.
Export the router's
type: it is all the client needs.src/server.tsconstt =const t: TrpcRoot<{ userId: string | null; }, {}, RootDef<{ userId: string | null; }, {}>>initTrpc<{initTrpc<{ ctx: { userId: string | null; }; }>(opts?: RootOptions<{}>): TrpcRoot<{ userId: string | null; }, {}, RootDef<{ userId: string | null; }, {}>>ctx: {ctx: { userId: string | null; }userId: string | nulluserId: string | null } }>(); export constappRouter = {const appRouter: { post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }post: {post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }byId:byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>t.const t: TrpcRoot<{ userId: string | null; }, {}, RootDef<{ userId: string | null; }, {}>>procedure .TrpcRoot<{ userId: string | null; }, {}, RootDef<{ userId: string | null; }, {}>>.procedure: ProcedureBuilder<RootDef<{ userId: string | null; }, {}>>input(ProcedureBuilder<RootDef<{ userId: string | null; }, {}>>.input<z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>(schema: z.ZodObject<{ id: z.ZodString; }, z.core.$strip>): ProcedureBuilder<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>> (+1 overload)import zz.object({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>id: z.ZodStringid:import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() })) .query( (ProcedureBuilder<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>>.query<Post | TrpcError<"NOT_FOUND", { id: string; }>>(resolver: (opts: ResolverOpts<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>>) => Post | TrpcError<"NOT_FOUND", { id: string; }>): BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>opts) =>opts: ResolverOpts<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>>const posts: Map<string, Post>posts.Map<string, Post>.get(key: string): Post | undefinedget(opts.opts: ResolverOpts<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>>input.ResolverOpts<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>>.input: { id: string; }id: stringid) ??error({error<"NOT_FOUND", { id: string; }>(opts: Omit<TrpcErrorOptions<"NOT_FOUND", { id: string; }>, "defined">): TrpcError<"NOT_FOUND", { id: string; }>code: "NOT_FOUND"code: 'NOT_FOUND',data: {data?: { id: string; } | undefinedid: stringid:opts.opts: ResolverOpts<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>>input.ResolverOpts<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>>.input: { id: string; }id: stringid } }), ),create:create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>t.const t: TrpcRoot<{ userId: string | null; }, {}, RootDef<{ userId: string | null; }, {}>>procedure .TrpcRoot<{ userId: string | null; }, {}, RootDef<{ userId: string | null; }, {}>>.procedure: ProcedureBuilder<RootDef<{ userId: string | null; }, {}>>input(ProcedureBuilder<RootDef<{ userId: string | null; }, {}>>.input<z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>(schema: z.ZodObject<{ title: z.ZodString; }, z.core.$strip>): ProcedureBuilder<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>> (+1 overload)import zz.object({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>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) })) .mutation((ProcedureBuilder<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ title: ZodString; }, $strip>>>.mutation<Post>(resolver: (opts: ResolverOpts<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>>) => Post): BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>opts) => { constopts: ResolverOpts<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>>const post: Postpost: Post = {Post.id: stringid:var crypto: Cryptocrypto.Crypto.randomUUID(): `${string}-${string}-${string}-${string}-${string}`randomUUID(),Post.title: stringtitle:opts.opts: ResolverOpts<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>>input.ResolverOpts<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ title: ZodString; }, $strip>>>.input: { title: string; }title: stringtitle };const posts: Map<string, Post>posts.Map<string, Post>.set(key: string, value: Post): Map<string, Post>set(const post: Postpost.Post.id: stringid,const post: Postpost); returnconst post: Postpost; }), }, }; export typeAppRouter = typeoftype AppRouter = { post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }appRouter;const appRouter: { post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }Serve them anywhere
AdapterscreateFetchHandlerserves the router over HTTP, SSE and WebSockets. Handhandler.fetchto Workers, Bun, Deno or a Next.js route, or serve it on Node with Effect'sHttpServer.src/server.tsexport constconst handler: FetchHandler<undefined>handler =createFetchHandler({createFetchHandler<{ post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }, undefined>(opts: FetchHandlerOptions<...>): FetchHandler<...>router:BaseHandlerOptions<{ post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { ...; }>; create: BuildProcedure<...>; }; }>.router: { post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }appRouter,const appRouter: { post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ id: z.ZodString; }, z.core.$strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, z.ZodObject<{ title: z.ZodString; }, z.core.$strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }createContext: (createContext: (opts: CreateContextOpts<undefined>) => MaybePromise<{ userId: string | null; }>opts: CreateContextOpts<undefined>opts) => ({userId: string | nulluserId:opts: CreateContextOpts<undefined>opts.CreateContextOpts<undefined>.request: HttpServerRequestrequest.HttpIncomingMessage<HttpServerError>.headers: Headersheaders['x-user-id'] ?? null, }), }); // Workers, Bun, Deno, Next.js route handlers: any fetch runtime. export default {fetch: (request: Request) => Promise<Response>fetch:const handler: FetchHandler<undefined>handler.FetchHandler<undefined>.fetch: (request: Request) => Promise<Response>fetch };Call them with their types
The clientThe client is typed from
routerType<AppRouter>(), so no server code ships to the browser. Hover a name to see its type.safe()turns a call into a result tuple, with the procedure's defined errors narrowed bycode.src/client.tsimport {function createTrpcClient<TSource, const TLinks extends ReadonlyArray<TrpcLink<any, RouterOf<TSource>>>>(opts: TypedClientOptions<TSource, TLinks> & DuplicateSwitchCheck<TLinks> & UnmetNeedCheck<TLinks>): TrpcClient<RouterOf<TSource>, LinksContext<TLinks>, undefined, LinksTypes<TLinks>>createTrpcClient,function httpLink<TOpts extends HttpLinkOptions>(opts: TOpts): TrpcLink<HttpLinkDecl<TOpts>>httpLink,function routerType<TRouter>(): RouterType<TRouter>routerType } from '@trpc12/trpc/client'; import {safe } from '@trpc12/trpc/client'; import type {function safe<T, E = unknown>(promise: PromiseLike<T> & { readonly "~error"?: E; }): Promise<SafeResult<T, E>>AppRouter } from './server.ts'; consttype AppRouter = { post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ title: ZodString; }, $strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }client =const client: TrpcClient<{ post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ title: ZodString; }, $strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }, {}, undefined, LinksTypes<...>>createTrpcClient({createTrpcClient<RouterType<{ post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ title: ZodString; }, $strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }>, readonly [...]>(opts: TypedClientOptions<...>): TrpcClient<...>router:TypedClientOptions<RouterType<{ post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { ...; }>; create: BuildProcedure<...>; }; }>, readonly [...]>.router: RouterType<{ post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ title: ZodString; }, $strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }>routerType<routerType<{ post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ title: ZodString; }, $strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }>(): RouterType<...>AppRouter>(),type AppRouter = { post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ title: ZodString; }, $strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }links: [TypedClientOptions<RouterType<{ post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { ...; }>; create: BuildProcedure<...>; }; }>, readonly [...]>.links: readonly [TrpcLink<{ form: JsonForm; provides: "response"; }, any>]httpLink({httpLink<{ url: string; }>(opts: { url: string; }): TrpcLink<{ form: JsonForm; provides: "response"; }, any>url: stringurl: '/trpc' })], }); const post = awaitclient.const client: TrpcClient<{ post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ title: ZodString; }, $strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }, {}, undefined, LinksTypes<...>>post.post: ClientRouter<{ byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ title: ZodString; }, $strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }, {}, undefined, LinksTypes<...>>byId.byId: { query(input: { id: string; }, opts?: CallOptions<{}> | undefined): TrpcPromise<{ id: string; title: string; }, CallError<{ type: "query"; ctx: { userId: string | null; }; meta: {}; input: { id: string; }; output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }> | InputValidationError<{ id: string; }>; services: never; }, JsonForm, never>>; }query({function query(input: { id: string; }, opts?: CallOptions<{}> | undefined): TrpcPromise<{ id: string; title: string; }, CallError<{ type: "query"; ctx: { userId: string | null; }; meta: {}; input: { id: string; }; output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }> | InputValidationError<{ id: string; }>; services: never; }, JsonForm, never>>id: stringid: '1' });var console: Consoleconsole.Console.log(...data: any[]): voidlog(post.const post: { id: string; title: string; }title: stringtitle); const [created,const created: { id: string; title: string; } | undefinederr] = awaitconst err: UnexpectedError | (TrpcError<"BAD_REQUEST", { readonly issues: readonly InputIssue<{ title: string; }>[]; }> & { readonly defined: true; }) | undefinedsafe(safe<{ id: string; title: string; }, UnexpectedError | (TrpcError<"BAD_REQUEST", { readonly issues: readonly InputIssue<{ title: string; }>[]; }> & { readonly defined: true; })>(promise: PromiseLike<{ id: string; title: string; }> & { readonly '~error'?: UnexpectedError | (TrpcError<"BAD_REQUEST", { readonly issues: readonly InputIssue<{ title: string; }>[]; }> & { readonly defined: true; }) | undefined; }): Promise<SafeResult<{ id: string; title: string; }, UnexpectedError | (TrpcError<"BAD_REQUEST", { readonly issues: readonly InputIssue<{ title: string; }>[]; }> & { readonly defined: true; })>>client.const client: TrpcClient<{ post: { byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ title: ZodString; }, $strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }; }, {}, undefined, LinksTypes<...>>post.post: ClientRouter<{ byId: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }>; services: never; unmapped: never; }>; create: BuildProcedure<InputDef<RootDef<{ userId: string | null; }, {}>, ZodObject<{ title: ZodString; }, $strip>>, "mutation", { output: Post; errors: never; services: never; unmapped: never; }>; }, {}, undefined, LinksTypes<...>>create.create: { mutate(input: { title: string; }, opts?: CallOptions<{}> | undefined): TrpcPromise<{ id: string; title: string; }, CallError<{ type: "mutation"; ctx: { userId: string | null; }; meta: {}; input: { title: string; }; output: Post; errors: InputValidationError<{ title: string; }>; services: never; }, JsonForm, never>>; }mutate({function mutate(input: { title: string; }, opts?: CallOptions<{}> | undefined): TrpcPromise<{ id: string; title: string; }, CallError<{ type: "mutation"; ctx: { userId: string | null; }; meta: {}; input: { title: string; }; output: Post; errors: InputValidationError<{ title: string; }>; services: never; }, JsonForm, never>>title: stringtitle: '' })); if (err?.const err: UnexpectedError | (TrpcError<"BAD_REQUEST", { readonly issues: readonly InputIssue<{ title: string; }>[]; }> & { readonly defined: true; }) | undefineddefined: boolean | undefineddefined &&err.const err: TrpcError<"BAD_REQUEST", { readonly issues: readonly InputIssue<{ title: string; }>[]; }> & { readonly defined: true; }TrpcError<"BAD_REQUEST", { readonly issues: readonly InputIssue<{ title: string; }>[]; }>.code: "BAD_REQUEST"code === 'BAD_REQUEST') {var console: Consoleconsole.Console.log(...data: any[]): voidlog(err.const err: TrpcError<"BAD_REQUEST", { readonly issues: readonly InputIssue<{ title: string; }>[]; }> & { readonly defined: true; }data.TrpcError<"BAD_REQUEST", { readonly issues: readonly InputIssue<{ title: string; }>[]; }>.data: { readonly issues: readonly InputIssue<{ title: string; }>[]; }issues); } else if (issues: readonly InputIssue<{ title: string; }>[]created) {const created: { id: string; title: string; } | undefinedvar console: Consoleconsole.Console.log(...data: any[]): voidlog(created.const created: { id: string; title: string; }id: stringid); }
What's in v12
One package, @trpc12/trpc, with an entry point per job and nothing to install twice.
- Built on EffectProcedures can return an Effect, and the client can be one. Middleware, services and errors compose with the rest of your Effect app.
- Streams and filesReturn an async iterable to stream; send and receive files and blobs. Every link batches and streams by default.
- Subscriptions that resume
tracked()events resume after a reconnect, over SSE or WebSockets, fed by aPublisherbacked by Redis, Postgres or Durable Objects. - OpenAPI from the same routerGive a procedure a route and serve the same router as REST, with an OpenAPI document generated from its schemas or its types.
- Every runtimeNode, Bun, Deno, Cloudflare Workers and Durable Objects, AWS Lambda, Express, Fastify, Firebase, message ports and raw sockets.
- Framework integrationsQuery and mutation options for TanStack Query in React, Vue, Solid, Svelte, Angular, Preact and Lit; Server Actions and RSC helpers.
- Contract-firstWrite the contract first and implement it later, or derive one from an OpenAPI document. Clients type from the contract alone.
- Migrate one step at a time
createV11Handlerserves v11 clients from a v12 router while you migrate, and a codemod rewrites most of the code.