Client
Client is the central class for configuring your server connection in Hyper Fetch. It initializes all core
subsystems—such as queues, cache, and interceptors—and provides a unified way to create and manage requests based on its
configuration. By keeping all data and logic encapsulated within a single client instance, you ensure that each client
remains isolated and independent from others.
- Builder for all of the requests
- Central place for all the default settings
- Handles authentication and interceptors
- Place where all sub-modules are initialized
Initialization
The client is initialized with the createClient function.
import { createClient } from "@hyper-fetch/core";
export const client = createClient({ url: "http://localhost:3000" });
Available methods
| Name | Description |
|---|---|
| |
| Set a custom mapper that generates the unique request ID for deduplication and tracking. |
| Set a custom mapper that generates the query key used to identify requests in the dispatcher queue. |
| Set the new logger instance to the Client |
| Set the logger severity of the messages displayed to the console |
| Set globally if mocking should be enabled or disabled for all client requests. |
| This method enables the logger usage and display the logs in console |
| Set a custom mapper that generates the cache key used to store and retrieve cached responses. |
| Set custom http adapter to handle graphql, rest, firebase or others |
| Set a custom mapper that generates the abort key used to cancel in-flight requests. |
| Remove plugins from Client |
| Method for removing listeners on success. |
| Method for removing response interceptors. |
| Method for removing listeners on request. |
| Method for removing listeners on error. |
| Method for removing listeners on auth. |
| Method for intercepting success responses. |
| Method for intercepting any responses. |
| Method of manipulating requests before sending the request. |
| Method for intercepting error responses. It can be used for example to refresh tokens. |
| Method of manipulating requests before sending the request. We can for example add custom header with token to the request which request had the auth set to true. |
| Hydrate your SSR cache data |
| Reconstruct a Request class instance from its serialized JSON form. Useful when dispatcher storage serializes queue data (e.g. MMKV, AsyncStorage) and the deserialized request loses its class identity. |
| Create requests based on the Client setup |
| Clears the Client instance and remove all listeners on it's dependencies |
| Add persistent plugins which trigger on the request lifecycle |
Features
Here are some of the most important features available in the Client class.
The Client is designed to be used as a builder of requests, serving as the global entry point for server communication
throughout your application. This approach prevents duplicated logic, promotes a consistent architecture, and makes it
easier to maintain and test your code by centralizing configuration and types.
-
createRequest()
The createRequest method is the main method for creating templates for the API connectivity. We use the builder
pattern to create a request object which contain crucial information about the request - endpoint, method, etc.
Each request keep the reference to the client, so it can access the global settings.
import { createClient } from "@hyper-fetch/core";
const client = createClient({ url: "http://localhost:3000" });
const getUser = client.createRequest<{ response: User }>()({ endpoint: "/users/1" });
console.log(getUser);
-
Authentication
To send authenticated requests, set up the onAuth interceptor. Set up the request with the auth option set to
true.
import { createClient } from "@hyper-fetch/core";
import { store } from "./store"; // Your state management store
export const client = createClient({ url: "http://localhost:3000" }).onAuth((request) => {
// Get the auth token from your store
const state = store.getState();
const authToken = state.auth.token;
// Add the token to the request headers
return request.setHeaders({
...request.headers,
Authorization: `Bearer ${authToken}`,
});
});
// Create an authenticated request
export const getUsers = client.createRequest()({
endpoint: "/users",
auth: true, // This enables the onAuth interceptor
});
-
Interceptors
Interceptors are a powerful feature that allows you to modify requests before they're sent or responses before they are processed within other core modules.
They can modify the request before it's sent or the response after it's received, depending on the type of interceptor.
Request Interceptor
onRequest()- allows to modify the request before it's sent giving ability to modify its dataonAuth()- allows to modify the request withauth: truebefore it's sent, giving ability to pass custom auth headers or other any options
Response Interceptor
There are several methods for intercepting a response from a request:
onResponse()- intercepts any response after it's received to modify it's bodyonError()- intercepts the error response after it's received to modify it's bodyonSuccess()- intercepts the success response after it's received to modify it's body
We can modify received data with these interceptors before it will be emitted to other sub-systems.
import { createClient } from "@hyper-fetch/core";
export const client = createClient({ url: "http://localhost:3000" })
// Add request interceptor
.onRequest((request) => {
// Add custom headers to all requests
return request.setHeaders({
...request.headers,
"x-custom-header": "custom-value",
});
})
// Add response interceptor
.onResponse(async (response, request) => {
// Log all responses
console.log(`Response for ${request.endpoint}:`, response);
return response;
})
// Add error interceptor
.onError(async (errorResponse, request) => {
// Handle specific error responses
if (errorResponse.status === 401 && !request.used) {
// Implement token refresh logic
// Set Used allows you to prevent infinite loops of retries
return request.setUsed(true).send();
}
return errorResponse;
});
-
Global settings
Every module is initialized inside of the Client class. This is a perfect place to store and control global settings
for the adapter, cache, requests or other modules.
import { createClient } from "@hyper-fetch/core";
export const client = createClient({
url: "http://localhost:3000",
});
client.adapter.setRequestDefaults({
retries: 3, // Retry failed requests 3 times
cacheTime: 5 * 60 * 1000, // Cache data for 5 minutes
staleTime: 60 * 1000, // Data becomes stale after 1 minute
// Adapter options
options: {
timeout: 10000, // 10 seconds timeout for all requests
},
});
// These global settings can be overridden for specific requests
export const getUser = client.createRequest()({
endpoint: "/users/:id",
method: "GET",
timeout: 5000, // Override the global timeout of 10000 for this request
});
-
Cache modes (
auto/client/server)
createClient accepts an optional mode:
"auto"(default when omitted) — resolves to"client"in the browser and"server"in Node.js."client"or"server"— forces that effective mode regardless of environment.
client.mode is always the resolved value: "client" or "server". Cache behavior follows that resolved mode
(on the server, caching is disabled by default unless you use setScope).
import { createClient } from "@hyper-fetch/core";
const client = createClient({ url: "http://localhost:3000" });
// Same as { mode: "auto" } — client.mode is "client" in browser, "server" in Node.js
// Override when needed (e.g. tests or forcing server rules in the browser)
createClient({ url: "http://localhost:3000", mode: "server" });
createClient({ url: "http://localhost:3000", mode: "client" });
On the server, use setScope to explicitly opt in to caching with per-user/session isolation:
const getUser = client.createRequest<{ response: User }>()({
endpoint: "/users/:userId",
method: "GET",
});
// Server-side: NOT cached (safe by default)
getUser.setParams({ userId: 1 }).send();
// Server-side: CACHED — scoped to this user's session
getUser
.setParams({ userId: 1 })
.setScope(req.session.id)
.send();
Without setScope, responses are never cached on the server. This prevents user A from seeing user B's data.
Always scope to the current user/session context in server environments.
-
Plugins
Plugins are a powerful feature that allows you to extend the functionality of the client. They can be used to listen to the events happening inside of the Hyper Fetch.
client.setDebug(true) must be called for any logger output to appear. Without it, the LoggerManager silently
discards all log messages regardless of log level. Enable it during development or when using the devtools plugin.
const client = createClient({ url: "http://localhost:3000" }).setDebug(true);
import { createClient } from "@hyper-fetch/core";
import { Plugin } from "@hyper-fetch/core";
import { DevtoolsPlugin } from "@hyper-fetch/plugin-devtools";
// Add the official devtools plugin
export const client = createClient({ url: "http://localhost:3000" })
.setDebug(true)
.addPlugin(
DevtoolsPlugin({
appName: "My Application",
}),
);
// Create a custom plugin
const loggingPlugin = new Plugin({
name: "logging-plugin",
})
.onRequestStart(({ request }) => {
console.log(`Request started: ${request.method} ${request.endpoint}`);
})
.onRequestSuccess(({ response, request }) => {
console.log(`Request succeeded: ${request.method} ${request.endpoint}`, response.data);
})
.onRequestError(({ response, request }) => {
console.error(`Request failed: ${request.method} ${request.endpoint}`, response.error);
});
// Add the custom plugin to the client
client.addPlugin(loggingPlugin);
Removing Plugins
Plugins can be removed at runtime. When a plugin is removed, its onUnmount hook is called automatically.
client.removePlugin(loggingPlugin);
-
Clearing the Client
The clear() method aborts all in-flight requests, clears both dispatchers and the cache, then recreates all
subsystems from the original factory options. This is important for testing teardown and SSR cleanup to prevent state
leaking between requests.
// Abort everything, clear cache and queues, reinitialize subsystems
client.clear();
-
Restoring Requests from JSON
The fromJSON method restores a Request instance from its serialized JSON representation. This is essential for
persistence and offline scenarios where requests are stored (e.g. in localStorage) and need to be recreated later.
// Serialize a request
const json = getUser.toJSON();
// Later, restore it
const restored = client.fromJSON(json);
await restored.send();
You can also use the static method directly: Request.fromJSON(client, json).
-
Interceptor Removal
Each onAuth, onRequest, onResponse, onError, and onSuccess call stores the callback. You can remove
specific interceptors later for cleanup or dynamic reconfiguration.
const authInterceptor = (request) => {
return request.setHeaders({ Authorization: `Bearer ${token}` });
};
// Register
client.onAuth(authInterceptor);
// Remove later
client.removeOnAuthInterceptors([authInterceptor]);
Available removal methods:
| Method | Removes interceptors from |
|---|---|
removeOnAuthInterceptors | onAuth |
removeOnRequestInterceptors | onRequest |
removeOnResponseInterceptors | onResponse |
removeOnErrorInterceptors | onError |
removeOnSuccessInterceptors | onSuccess |
-
Hydration
We can hydrate the client with the data from the server. This is a powerful feature that allows us to pass the data from the server to the client for the SSR use cases.
// Server-side code
import { createClient } from "@hyper-fetch/core";
export const client = createClient({ url: "http://localhost:3000" });
const userRequest = client.createRequest()({ endpoint: "/users/1" });
// Fetch data on the server
const response = await userRequest.send();
// Dehydrate the response for client hydration
const dehydratedData = userRequest.dehydrate({ response });
// Pass dehydratedData to the client (e.g., as props or through a global state)
// *****************************************************
// *****************************************************
// Client-side code
// *****************************************************
// *****************************************************
import { createClient } from "@hyper-fetch/core";
import { useFetch } from "@hyper-fetch/react";
export const client = createClient({ url: "http://localhost:3000" });
// Hydrate the client with server data
client.hydrate([dehydratedData]);
// In your React component
function UserProfile() {
// Data will be available immediately from cache, no loading state needed
const { data } = useFetch(client.createRequest()({ endpoint: "/users/1" }), {
revalidate: false, // Prevent automatic refetching
});
return <div>{data?.name}</div>;
}
TypeScript
Most important type passed to the createClient is the error type. It is a global error type, being used like a default
for any request errors. It is later combined with the "local error" type of the requests creating the union. This way we
handle generic errors from the server (global) as well as some validation/business (local) errors for given endpoint.
It is important to use the eslint plugin to get the best typescript experience. We enhance the abilities of the object generics to provide you with the best possible DX.
import { createClient, AdapterType } from "@hyper-fetch/core";
// Define your global error type
interface ClientErrorType {
message: string;
code: number;
}
const client = createClient<{ error: ClientErrorType }>();
-
errordefines the global/default error type used in all the requests. It should consist of anErrortype and your defaultServerErrorType. It is propagated down to thecreateRequestmethod, serving as a default for theglobalErrortype. -
adapteris the generic responsible for shaping options passed to the adapter. Most likely you will change it only when you provide your custom adapter.
Modules
Client is a place where all the subsystems are initialized. Here is a diagram of the client and its subsystems:
