---
name: client-setup
description: Create a vanilla tRPC client with createTRPCClient<AppRouter>() and a terminating link such as httpBatchLink, import AppRouter with `import type`, set dynamic auth headers, and put the transformer on links (not the client constructor). Infer types with inferRouterInputs and inferRouterOutputs, handle TRPCClientError with try/catch or safe(), and abort calls with an AbortSignal.
requires:
  - server-setup
sources:
  - www/v11/docs/client/vanilla/setup.md
  - www/v11/docs/client/vanilla/infer-types.md
  - www/v11/docs/client/vanilla/error-handling.md
  - www/v11/docs/client/vanilla/aborting-procedure-calls.md
  - www/v11/docs/client/headers.md
metadata:
  type: core
  library: trpc
  library_version: 11.23.1-canary.16.f11951ce3
---

<!-- Generated from www/v11/docs/client/vanilla/setup.md by www/build/skills.ts. Edit the docs instead. -->

# Set up a tRPC Client

From https://trpc.io/docs/client/vanilla/setup

### 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.

```bash
npm install @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.

```ts no-check title="utils/trpc.ts"
import type { AppRouter } from '../server/router';
```

> [!TIP]
> By using `import type` you ensure that the reference will be stripped at compile-time, meaning you don't inadvertently import server-side code into your client. For more information, [see the Typescript docs](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export).


### 3. Initialize the tRPC client

Create a tRPC client with the `createTRPCClient` method, and add a `links` array with a [terminating link](https://trpc.io/docs/client/links#the-terminating-link) pointing at your API. To learn more about tRPC links, [click here](https://trpc.io/docs/client/links).

```ts no-check title="client.ts"
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';

const client = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:3000/trpc',

      // You can pass any HTTP headers you wish here
      async headers() {
        return {
          authorization: getAuthCookie(),
        };
      },
    }),
  ],
});
```

### 4. Use the tRPC Client

Under the hood this creates a typed [JavaScript Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) which allows you to interact with your tRPC API in a fully type-safe way:

```ts no-check title="client.ts"
const bilbo = await client.getUser.query('id_bilbo');
// => { id: 'id_bilbo', name: 'Bilbo' };

const frodo = await client.createUser.mutate({ name: 'Frodo' });
// => { id: 'id_frodo', name: 'Frodo' };
```

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](https://trpc.io/docs/server/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](https://trpc.io/docs/rpc) 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).

## Inferring Types

From https://trpc.io/docs/client/vanilla/infer-types

It is often useful to access the types of your API within your clients. For this purpose, you are able to infer the types contained in your `AppRouter`.

`@trpc/server` exports the following helper types to assist with inferring these types from the `AppRouter` exported by your `@trpc/server` router:

- `inferRouterInputs<TRouter>`
- `inferRouterOutputs<TRouter>`
- `inferProcedureInput<TProcedure>`
- `inferProcedureOutput<TProcedure>`
- `inferSubscriptionInput<TProcedure>`
- `inferSubscriptionOutput<TProcedure>`

### Inferring Input & Output Types

Let's assume we have this example router:

```ts no-check title="server.ts"
import { initTRPC } from '@trpc/server';
import { z } from "zod";

const t = initTRPC.create();

const appRouter = t.router({
  post: t.router({
    list: t.procedure
      .query(() => {
        // imaginary db call
        return [{ id: 1, title: 'tRPC is the best!' }];
    }),
    byId: t.procedure
      .input(z.string())
      .query((opts) => {
        // imaginary db call
        return { id: 1, title: 'tRPC is the best!' };
    }),
    create: t.procedure
      .input(z.object({ title: z.string(), text: z.string(), }))
      .mutation((opts) => {
        // imaginary db call
        return { id: 1, ...opts.input };
    }),
    onPostAdd: t.procedure
      .input(z.object({ authorId: z.string() }))
      .subscription(async function* ({ input }) {
        // imaginary event source
        yield {
          id: 1,
          title: 'tRPC is the best!',
          authorId: input.authorId,
        };
    }),
  }),
});

export type AppRouter = typeof appRouter;
```

Using the helpers, we can infer the types of our router. The following example shows how to infer the types of the `post.create` procedure:

```ts no-check title="client.ts"
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from './server';

type RouterInput = inferRouterInputs<AppRouter>;
type RouterOutput = inferRouterOutputs<AppRouter>;

type PostCreateInput = RouterInput['post']['create'];
type PostCreateOutput = RouterOutput['post']['create'];
```

### Inferring Individual Procedure Types

If you already have access to a specific procedure on your router, you can infer its input or output directly:

```ts no-check title="client.ts"
import type {
  inferProcedureInput,
  inferProcedureOutput,
} from '@trpc/server';
import type { AppRouter } from './server';

type PostByIdInput = inferProcedureInput<AppRouter['post']['byId']>;
type PostByIdOutput = inferProcedureOutput<AppRouter['post']['byId']>;
```

For subscriptions, you can infer the subscription input and the emitted data type:

```ts no-check title="client.ts"
import type {
  inferSubscriptionInput,
  inferSubscriptionOutput,
} from '@trpc/server';
import type { AppRouter } from './server';

type OnPostAddInput = inferSubscriptionInput<AppRouter['post']['onPostAdd']>;
type OnPostAddOutput = inferSubscriptionOutput<AppRouter['post']['onPostAdd']>;
```

### Infer `TRPCClientError` types

It's also useful to infer the error type for your `AppRouter`

```ts no-check title="client.ts"
import { TRPCClientError } from '@trpc/client';
import type { AppRouter } from './server';
import { trpc } from './trpc';

export function isTRPCClientError(
  cause: unknown,
): cause is TRPCClientError<AppRouter> {
  return cause instanceof TRPCClientError;
}

async function main() {
  try {
    await trpc.post.byId.query('1');
  } catch (cause) {
    if (isTRPCClientError(cause)) {
      // `cause` is now typed as your router's `TRPCClientError`
      console.log('data', cause.data);
    } else {
      // [...]
    }
  }
}

main();
```

## Error Handling

From https://trpc.io/docs/client/vanilla/error-handling

A failed procedure call rejects with a [`TRPCClientError`](https://trpc.io/docs/client/vanilla/infer-types#infer-trpcclienterror-types). You can either let it throw and catch it, or use `safe()` to get it back as a type-safe value.

### Catching a thrown error

`query()` and `mutate()` behave like any other promise, so a `try`/`catch` works as you would expect. TypeScript has no way to describe what a function throws, though, so `cause` arrives as `unknown` and has to be narrowed:

```ts no-check title="client.ts"
import { TRPCClientError } from '@trpc/client';
import type { AppRouter } from './server';
import { trpc } from './trpc';

try {
  const post = await trpc.post.byId.query('1');
} catch (cause) {
  if (cause instanceof TRPCClientError) {
    console.log(cause.data);
  } else {
    // something that isn't a tRPC error
  }
}
```

See [Inferring Types](https://trpc.io/docs/client/vanilla/infer-types#infer-trpcclienterror-types) for a reusable `isTRPCClientError` guard.

### Errors as values with `safe()`

`safe()` awaits a `query()` or `mutate()` call without letting it throw, handing back a `[data, error]` pair instead. Exactly one side is ever set, so checking either one narrows the other:

```ts no-check title="client.ts"
import { safe } from '@trpc/client';
import { trpc } from './trpc';

const [post, error] = await safe(trpc.post.byId.query('1'));

if (error) {
  console.log(error.data);
} else {
  console.log(post.title);
}
```

> [!NOTE] Info
> Subscriptions don't need `safe()` - they never throw at the call site, and report failures through `onError`, which is already typed with the procedure's error shape.

## Aborting Procedure Calls

From https://trpc.io/docs/client/vanilla/aborting-procedure-calls

tRPC supports the standard `AbortController`/`AbortSignal` API for aborting procedures. All you have to do is pass an `AbortSignal` to the query or mutation options, and call the `AbortController` instance's `abort` method if you need to cancel the request.

```ts no-check title="utils.ts"
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';

const client = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:3000/trpc',
    }),
  ],
});

// 1. Create an AbortController instance - this is a standard javascript API
const ac = new AbortController();

// 2. Pass the signal to a query or mutation
const query = client.userById.query('id_bilbo', { signal: ac.signal });

// 3. Cancel the request if needed
ac.abort();
```

## Headers

From https://trpc.io/docs/client/headers

The headers option may be used with any of our HTTP links: [`httpBatchLink`](https://trpc.io/docs/client/links/httpBatchLink), [`httpBatchStreamLink`](https://trpc.io/docs/client/links/httpBatchStreamLink), [`httpLink`](https://trpc.io/docs/client/links/httpLink), or [`httpSubscriptionLink`](https://trpc.io/docs/client/links/httpSubscriptionLink).

`headers` can be both an object or a function. If it's a function it will get called dynamically for every HTTP request.

```ts no-check title="utils/trpc.ts"
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';

let token: string;

export function setToken(newToken: string) {
  /**
   * You can also save the token to cookies, and initialize from
   * cookies above.
   */
  token = newToken;
}

export const trpc = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:3000',
      /**
       * Headers will be called on each request.
       */
      headers() {
        return {
          Authorization: token,
        };
      },
    }),
  ],
});
```

### Example with auth login

```ts no-check title="auth.ts"
const result = await trpc.auth.login.mutate({ username: 'user', password: 'pass' });
setToken(result.accessToken);
```

The `token` can be whatever you want it to be. It's entirely up to you whether that's just a client-side
variable that you update the value of on success or whether you store the token and pull it from local storage.

## Related skills

- `server-setup` (https://trpc.io/docs/server/overview): `npx @tanstack/intent@latest load @trpc/server#server-setup`
