Skip to content
tRPC

Partners

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/client
npm install @trpc/server @trpc/client
yarn add @trpc/server @trpc/client
bun add @trpc/server @trpc/client

2. Import your App Router

Import your AppRouter type into the client application. This type holds the shape of your entire API.

utils/trpc.ts
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.

client.ts
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:

client.ts
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:

client.ts
// 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
) {
const user: {
    name: string;
}
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 loop

When 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 no unsubscribe(), like Node's events.on(). Stop it from inside the loop with break or return, or from outside by aborting the signal you 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 second for await over 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 a signal and abort() it instead.
  • Leaving the loop with break, return or a thrown error unsubscribes and drops the queued data.
  • Aborting the signal unsubscribes, 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 AppRouter type parameter. createTRPCClient({ links }) without <AppRouter> compiles, but none of your procedures are typed. If the AppRouter import doesn't resolve (often a monorepo path or tsconfig issue), fix the import rather than reaching for createTRPCClient<any>.
  • Importing the router value instead of its type. import { appRouter } from '../server/router' pulls your server code into the client bundle. Export type AppRouter = typeof appRouter from the server and import it with import type.
  • Passing transformer to createTRPCClient. 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 of createTRPCClient.
  • 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's url doesn't match where your adapter serves tRPC (check the /trpc prefix).