@trpc/client
Version:
318 lines (225 loc) • 7.84 kB
Markdown
---
name: client-setup
description: >
Create a vanilla tRPC client with createTRPCClient<AppRouter>(), configure
link chain with httpBatchLink/httpLink, dynamic headers for auth, transformer
on links (not client constructor). Infer types with inferRouterInputs and
inferRouterOutputs. AbortController signal support. TRPCClientError typing.
type: core
library: trpc
library_version: '11.16.0'
requires:
- server-setup
sources:
- www/docs/client/overview.md
- www/docs/client/vanilla/overview.md
- www/docs/client/vanilla/setup.mdx
- www/docs/client/vanilla/infer-types.md
- www/docs/client/headers.md
- packages/client/src/internals/TRPCUntypedClient.ts
---
# tRPC -- Client Setup
## Setup
```ts
// server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
user: t.router({
byId: t.procedure
.input(z.object({ id: z.string() }))
.query(({ input }) => ({ id: input.id, name: 'Bilbo' })),
create: t.procedure
.input(z.object({ name: z.string() }))
.mutation(({ input }) => ({ id: '1', ...input })),
}),
});
export type AppRouter = typeof appRouter;
```
```ts
// client.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';
const client = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
}),
],
});
const user = await client.user.byId.query({ id: '1' });
const created = await client.user.create.mutate({ name: 'Frodo' });
```
## Core Patterns
### Dynamic Auth Headers
```ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';
let token = '';
export function setToken(newToken: string) {
token = newToken;
}
export const client = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
headers() {
return {
Authorization: token ? `Bearer ${token}` : '',
};
},
}),
],
});
```
The `headers` callback is invoked on every HTTP request, so token changes take effect immediately.
### Inferring Procedure Input and Output Types
```ts
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from './server';
type RouterInput = inferRouterInputs<AppRouter>;
type RouterOutput = inferRouterOutputs<AppRouter>;
type UserCreateInput = RouterInput['user']['create'];
type UserByIdOutput = RouterOutput['user']['byId'];
```
### Aborting Requests with AbortController
```ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';
const client = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:3000/trpc' })],
});
const ac = new AbortController();
const query = client.user.byId.query({ id: '1' }, { signal: ac.signal });
ac.abort();
```
### Typed Error Handling
```ts
import { TRPCClientError } from '@trpc/client';
import type { AppRouter } from './server';
function isTRPCClientError(
cause: unknown,
): cause is TRPCClientError<AppRouter> {
return cause instanceof TRPCClientError;
}
try {
await client.user.byId.query({ id: '1' });
} catch (cause) {
if (isTRPCClientError(cause)) {
console.log('tRPC error code:', cause.data?.code);
}
}
```
## Common Mistakes
### [CRITICAL] Missing AppRouter type parameter on createTRPCClient
Wrong:
```ts
const client = createTRPCClient({ links: [httpBatchLink({ url })] });
```
Correct:
```ts
import type { AppRouter } from './server';
const client = createTRPCClient<AppRouter>({ links: [httpBatchLink({ url })] });
```
Without the type parameter, all procedure calls return `any` and type safety is completely lost.
Source: www/docs/client/vanilla/setup.mdx
### [CRITICAL] Transformer goes on individual links, not createTRPCClient
In v11, the `transformer` option is on individual terminating links, not the client constructor:
```ts
import superjson from 'superjson';
createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000',
transformer: superjson,
}),
],
});
```
In v11, the `transformer` option was moved from the client constructor to individual terminating links. Passing it to `createTRPCClient` throws a TypeError.
Source: packages/client/src/internals/TRPCUntypedClient.ts
### [CRITICAL] Transformer on server but not on client links
Wrong:
```ts
// Server: initTRPC.create({ transformer: superjson })
// Client:
httpBatchLink({ url: 'http://localhost:3000' });
```
Correct:
```ts
// Server: initTRPC.create({ transformer: superjson })
// Client:
httpBatchLink({ url: 'http://localhost:3000', transformer: superjson });
```
If the server uses a transformer, every terminating link on the client must also specify that transformer. Mismatch causes "Unable to transform response" errors.
Source: https://github.com/trpc/trpc/issues/7083
### [CRITICAL] Using import instead of import type for AppRouter
Wrong:
```ts
import { AppRouter } from '../server/router';
```
Correct:
```ts
import type { AppRouter } from '../server/router';
```
A non-type import pulls the entire server bundle into the client. Use `import type` so it is erased at build time.
Source: www/docs/client/vanilla/setup.mdx
### [CRITICAL] Importing appRouter value to derive type in client
Wrong:
```ts
import { appRouter } from '../server/router';
type AppRouter = typeof appRouter;
```
Correct:
```ts
// In server: export type AppRouter = typeof appRouter;
// In client:
import type { AppRouter } from '../server/router';
```
Importing the `appRouter` value (not just the type) bundles the entire server into the client, shipping server code to the browser.
Source: www/docs/server/routers.md
### [CRITICAL] Using type assertions to bypass AppRouter import errors
Wrong:
```ts
const client = createTRPCClient<any>({ links: [httpBatchLink({ url })] });
```
Correct:
```ts
// Fix the import path or monorepo configuration
import type { AppRouter } from '@myorg/api-types';
const client = createTRPCClient<AppRouter>({ links: [httpBatchLink({ url })] });
```
Casting to `any` or manually recreating the router type destroys end-to-end type safety. Fix the import path or monorepo config instead.
Source: www/docs/client/vanilla/setup.mdx
### [CRITICAL] Using createTRPCProxyClient (renamed in v11)
Wrong:
```ts
import { createTRPCProxyClient } from '@trpc/client';
```
Correct:
```ts
import { createTRPCClient } from '@trpc/client';
```
`createTRPCProxyClient` was renamed to `createTRPCClient` in v11.
Source: www/docs/client/vanilla/setup.mdx
### [CRITICAL] Treating tRPC as a REST API
Wrong:
```ts
fetch('/api/trpc/users/123', { method: 'GET' });
```
Correct:
```ts
const user = await client.user.byId.query({ id: '123' });
// Raw equivalent: GET /api/trpc/user.byId?input={"id":"123"}
```
tRPC uses JSON-RPC over HTTP. Procedures are called by dot-separated name with JSON input, not by REST resource paths.
Source: www/docs/client/overview.md
### [HIGH] HTML error page instead of JSON response
If you see `couldn't parse JSON, invalid character '<'`, the tRPC endpoint returned an HTML page (404/503) instead of JSON. This means the `url` in your link config is wrong or infrastructure routing is misconfigured -- it is not a tRPC bug. Verify the URL matches your adapter's mount point.
Source: www/docs/client/vanilla/setup.mdx
## See Also
- `links` -- configure httpBatchLink, httpLink, splitLink, and other link types
- `superjson` -- set up SuperJSON transformer on server and client
- `server-setup` -- define routers, procedures, context, and export AppRouter type
- `react-query-setup` -- use tRPC with TanStack React Query for React applications