Set up a tRPC Client
On this page
1. Install the tRPC Client library
Use your preferred package manager to install the @trpc/client library, and also install @trpc/server which contains some required types.
pnpm add @trpc/server @trpc/clientnpm install @trpc/server @trpc/clientyarn add @trpc/server @trpc/clientbun add @trpc/server @trpc/client2. Import your App Router
Import your AppRouter type into the client application. This type holds the shape of your entire API.
import type { type AppRouter = Router<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>> & DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>
AppRouter } from '../server/router';3. Initialize the tRPC client
Create a tRPC client with the createTRPCClient method, and add a links array with a terminating link pointing at your API. To learn more about tRPC links, click here.
import { function createTRPCClient<TRouter extends AnyRouter>(opts: CreateTRPCClientOptions<TRouter>): TRPCClient<TRouter>createTRPCClient, function httpBatchLink<TRouter extends AnyRouter>(opts: HTTPBatchLinkOptions<TRouter["_def"]["_config"]["$types"]>): TRPCLink<TRouter>httpBatchLink } from '@trpc/client';
import type { type AppRouter = Router<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter } from './server';
const const client: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
client = createTRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: CreateTRPCClientOptions<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>): TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<...>>>
createTRPCClient<type AppRouter = Router<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter>({
links: TRPCLink<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>[]
links: [
httpBatchLink<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: HTTPBatchLinkOptions<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>): TRPCLink<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
httpBatchLink({
url: string | URLurl: 'http://localhost:3000/trpc',
// You can pass any HTTP headers you wish here
async headers?: HTTPHeaders | ((opts: {
opList: NonEmptyArray<Operation>;
}) => HTTPHeaders | Promise<HTTPHeaders>) | undefined
headers() {
return {
authorization: stringauthorization: function getAuthCookie(): stringgetAuthCookie(),
};
},
}),
],
});4. Use the tRPC Client
Under the hood this creates a typed JavaScript Proxy which allows you to interact with your tRPC API in a fully type-safe way:
const const bilbo: {
id: string;
name: string;
}
bilbo = await const client: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
getUser: QueryProcedure<{
input: string;
output: {
id: string;
name: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
createUser: MutationProcedure<{
input: {
name: string;
};
output: {
name: string;
id: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>>
client.getUser: {
query: Resolver<{
input: string;
output: {
id: string;
name: string;
};
errorShape: DefaultErrorShape;
transformer: false;
}>;
}
getUser.query: (input: string, opts?: TRPCProcedureOptions) => TRPCTaggedPromise<{
id: string;
name: string;
}, DefaultErrorShape>
query('id_bilbo');
// => { id: 'id_bilbo', name: 'Bilbo' };
const const frodo: {
name: string;
id: string;
}
frodo = await const client: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
getUser: QueryProcedure<{
input: string;
output: {
id: string;
name: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
createUser: MutationProcedure<{
input: {
name: string;
};
output: {
name: string;
id: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>>
client.createUser: {
mutate: Resolver<{
input: {
name: string;
};
output: {
name: string;
id: string;
};
errorShape: DefaultErrorShape;
transformer: false;
}>;
}
createUser.mutate: (input: {
name: string;
}, opts?: TRPCProcedureOptions) => TRPCTaggedPromise<{
name: string;
id: string;
}, DefaultErrorShape>
mutate({ name: stringname: 'Frodo' });
// => { id: 'id_frodo', name: 'Frodo' };5. Use subscriptions
Given a subscription procedure and a link that supports subscriptions, such as httpSubscriptionLink or wsLink, .subscribe() starts the subscription. Either pass callbacks, or leave them out and iterate the result with for await:
// with callbacks
const const sub: Unsubscribablesub = const client: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
onUserAdd: SubscriptionProcedure<{
input: void;
output: AsyncIterable<{
name: string;
}, void, any>;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>>
client.onUserAdd: {
subscribe: SubscriptionResolver<{
input: void;
output: AsyncIterable<{
name: string;
}, never, any>;
errorShape: DefaultErrorShape;
transformer: false;
}>;
}
onUserAdd.subscribe: (input: void, opts: TRPCSubscriptionCallbacks<AsyncIterable<{
name: string;
}, never, any>, TRPCClientError<{
input: void;
output: AsyncIterable<{
name: string;
}, never, any>;
errorShape: DefaultErrorShape;
transformer: false;
}>> & TRPCProcedureOptions) => Unsubscribable (+1 overload)
subscribe(var undefinedundefined, {
function onData(user: {
name: string;
}): void
onData(user: {
name: string;
}
user) {
var console: Consoleconsole.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)log(user: {
name: string;
}
user);
},
});
// stop listening
const sub: Unsubscribablesub.Unsubscribable.unsubscribe(): voidunsubscribe();
// or with `for await`, without callbacks
const const ac: AbortControllerac = new var AbortController: new () => AbortControllerAbortController();
const const users: AsyncIterableIterator<{
name: string;
}>
users = const client: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
onUserAdd: SubscriptionProcedure<{
input: void;
output: AsyncIterable<{
name: string;
}, void, any>;
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>>
client.onUserAdd: {
subscribe: SubscriptionResolver<{
input: void;
output: AsyncIterable<{
name: string;
}, never, any>;
errorShape: DefaultErrorShape;
transformer: false;
}>;
}
onUserAdd.subscribe: <{
signal: AbortSignal;
}>(input: void, opts?: TRPCSubscriptionWithoutCallbacks<{
signal: AbortSignal;
}> | undefined) => AsyncIterableIterator<{
name: string;
}> (+1 overload)
subscribe(var undefinedundefined, { signal: AbortSignalsignal: const ac: AbortControllerac.AbortController.signal: AbortSignalsignal });
for await (const user of const users: AsyncIterableIterator<{
name: string;
}>
users) { var console: Consoleconsole.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)log(const user: {
name: string;
}
user);
}
// `ac.abort()` stops it from outside the loopWhen you iterate a subscription:
- A subscription is either iterated or uses callbacks. Iterating one that was started with callbacks throws, and is a type error.
- Without callbacks,
.subscribe()returns an async iterator with nounsubscribe(), like Node'sevents.on(). Stop it from inside the loop withbreakorreturn, or from outside by aborting thesignalyou passed. - Data is queued from when
.subscribe()is called until the loop reads it. The queue has no size limit. A subscription can be iterated once: a secondfor awaitover it throws. To consume it twice, call.subscribe()twice. - A subscription you never loop over or abort keeps running, so pass a
signal. - If you called
unsubscribe()on a subscription started without callbacks, pass asignalandabort()it instead. - Leaving the loop with
break,returnor a thrown error unsubscribes and drops the queued data. - Aborting the
signalunsubscribes, and the loop yields the data already queued before it ends. - An error from the subscription is thrown from the loop as a
TRPCClientError, after the data received before it.
You're all set!
Common mistakes
- Leaving out the
AppRoutertype parameter.createTRPCClient({ links })without<AppRouter>compiles, but none of your procedures are typed. If theAppRouterimport doesn't resolve (often a monorepo path ortsconfigissue), fix the import rather than reaching forcreateTRPCClient<any>. - Importing the router value instead of its type.
import { appRouter } from '../server/router'pulls your server code into the client bundle. Exporttype AppRouter = typeof appRouterfrom the server and import it withimport type. - Passing
transformertocreateTRPCClient. The transformer belongs on each terminating link (httpBatchLink,httpLink,wsLink, ...), and TypeScript rejects it on the client itself. See Data Transformers. - Using
createTRPCProxyClient. It's the v10 name, kept as a deprecated alias ofcreateTRPCClient. - Calling procedures like REST endpoints. Procedures are addressed by their dot-separated path, for example
GET /trpc/post.byId?input=..., not/trpc/post/1. Call them through the client, or see the HTTP RPC Specification for the wire format. - A JSON parse error like
Unexpected token '<'. The request reached something that answered with an HTML page, usually a 404, so the link'surldoesn't match where your adapter serves tRPC (check the/trpcprefix).