Fetch Client
Use rest-rpc with Fetch
initClient() creates a typed fetch client from a contract.
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.
const todo = await client.todos.get.fetch({ id: "todo_1" });
fetch()
Routes with one successful response expose fetch().
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().
const response = await client.todos.get.fetchResponse({
id: "todo_1",
});
fetchResponse() returns declared and undeclared responses as values.
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
const client = initClient(api, {
baseUrl: "https://api.example.com",
getGlobalHeaders: () => ({
authorization: `Bearer ${readToken()}`,
}),
timeoutMs: 10_000,
fetchOptions: {
credentials: "include",
},
});
Options include:
baseUrlfetchfetchOptionsgetGlobalHeaderstimeoutMsunknownRequestKeysvalidateResponses
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.
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.
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.
const client = initClient(api, {
baseUrl: "https://api.example.com",
unknownRequestKeys: "throw",
});
Use "strip" when extra keys should be ignored before building the HTTP request.
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 first.
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.
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.
["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.
await client.todos.get.fetch(
{ id: "todo_1" },
{
next: {
revalidate: 60,
tags: ["todos"],
},
},
);
"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().
const socket = client.todos.watch.openConnection({
id: "todo_1",
});
See WebSockets for details.