---
title: Fetch Client
description: Use rest-rpc with Fetch
---

`initClient()` creates a typed fetch client from a contract.

```ts
import { initClient } from "@rest-rpc/core";
import { api } from "./contract";

const client = initClient(api, {
	baseUrl: "https://api.example.com",
});
```

The client has the same route tree shape as the contract.

```ts
const todo = await client.todos.get.fetch({ id: "todo_1" });
```

## `fetch()`

Routes with one successful response expose `fetch()`.

```ts
const todo = await client.todos.get.fetch({
	id: "todo_1",
});
```

`fetch()` returns the successful response body directly.

It throws when the response is undeclared or is not a declared success response.

Use it when non-success responses should use the error path.

## `fetchResponse()`

Every HTTP route exposes `fetchResponse()`.

```ts
const response = await client.todos.get.fetchResponse({
	id: "todo_1",
});
```

`fetchResponse()` returns declared and undeclared responses as values.

```ts
if (response.declared && response.status === 200) {
	return response.body;
}

if (response.declared && response.status === 404) {
	return undefined;
}

throw new Error(`Unexpected response: ${response.status}`);
```

Use it when HTTP status is part of normal control flow.

## Client Options

```ts
const client = initClient(api, {
	baseUrl: "https://api.example.com",
	getGlobalHeaders: () => ({
		authorization: `Bearer ${readToken()}`,
	}),
	timeoutMs: 10_000,
	fetchOptions: {
		credentials: "include",
	},
});
```

Options include:

- `baseUrl`
- `fetch`
- `fetchOptions`
- `getGlobalHeaders`
- `timeoutMs`
- `unknownRequestKeys`
- `validateResponses`

`timeoutMs` limits the time spent waiting for the configured `fetch` function to
return a `Response`. It starts after `getGlobalHeaders()` resolves and is cleared
as soon as the response is received. It does not include response body parsing,
stream consumption, or other work before or after `fetch`. Pass an `AbortSignal`
in a route call when you need to cancel later response or stream consumption.

## Custom Fetch

If you need to wrap the fetch client with custom logic, pass a `fetch` function.

```ts
const client = initClient(api, {
	baseUrl: "https://api.example.com",
	fetch: async (url, init) => {
		const startedAt = performance.now();

		try {
			return await fetch(url, init);
		} finally {
			console.log("API request took", performance.now() - startedAt);
		}
	},
});
```

## Per-Call Fetch Options

Pass fetch options as the second argument.

```ts
const todo = await client.todos.get.fetch(
	{
		id: "todo_1",
		"x-request-id": "req_1",
	},
	{
		cache: "no-store",
	},
);
```

## Unknown Request Keys

By default, unknown request keys are rejected.

```ts
const client = initClient(api, {
	baseUrl: "https://api.example.com",
	unknownRequestKeys: "throw",
});
```

Use `"strip"` when extra keys should be ignored before building the HTTP request.

```ts
const client = initClient(api, {
	baseUrl: "https://api.example.com",
	unknownRequestKeys: "strip",
});
```

## Response Validation on the Client

It is possible to enable server output validation on the client by enabling `validateResponses`.
For understanding the implications of this option, read [Schemas](/docs/contract/schemas/) first.

```ts
const client = initClient(api, {
	baseUrl: "https://api.example.com",
	validateResponses: true,
});
```

This option also validates incoming WebSocket server messages on the client.

## Use in Next.js

When using the core fetch client with Next.js, the client can automatically add deterministic tags to GET
requests through Next's `fetch` options.

```ts
import { initClient } from "@rest-rpc/core";
import { api } from "./contract";

export const client = initClient(api, {
	baseUrl: "https://api.example.com",
	nextFetchTags: {
		enabled: true,
		tagPrefix: "api",
	},
	fetchOptions: {
		next: {
			revalidate: 60,
			tags: ["your-custom-tag"],
		},
	},
});
```

For `client.todos.get.fetch({ id: "todo_1" })`, the client attaches both the
exact request tag and the broader route tag.

```ts
["api:todos.get:id:todo_1", "api:todos.get"];
```

Use the route-level tag for broad invalidation. Use the exact request tag when
the request identifies a stable cache variant.

Automatic tags only include path params and query params. Request bodies are not
included because body-based requests generally should not be cached as GET
fetches. Headers are also excluded because they commonly contain sensitive data,
such as authorization or session values.

You can still pass Next.js fetch options normally through `fetchOptions`.
Generated tags are merged with any tags you provide.

```ts
await client.todos.get.fetch(
	{ id: "todo_1" },
	{
		next: {
			revalidate: 60,
			tags: ["todos"],
		},
	},
);
```

```ts
"use server";

import { revalidateTag } from "next/cache";
import { getNextFetchTags } from "@rest-rpc/core";
import { api } from "./contract";
import { client } from "./client";

export async function renameTodo(id: string, title: string) {
	await client.todos.update.fetch({ id, title });

	for (const tag of getNextFetchTags(api.todos.get, { id }, { tagPrefix: "api" })) {
		revalidateTag(tag);
	}
}
```

## WebSocket Routes

WebSocket routes expose `openConnection()` instead of `fetch()` and
`fetchResponse()`.

```ts
const socket = client.todos.watch.openConnection({
	id: "todo_1",
});
```

See [WebSockets](/docs/websockets/) for details.
