Skip to main content
Version: v7.0.0

Request

Read the API Reference »

The Request class is the cornerstone of Hyper Fetch's data-fetching system. It provides a powerful, type-safe way to define, configure, and manage API requests throughout your application. By encapsulating all the details needed to perform a request—such as endpoint, method, parameters, payload, and behavior - Request ensures consistency, predictability, and flexibility in how you interact with remote data sources.

With a strict and predictable data structure, Request makes it easy to serialize, persist, and rehydrate request states, enabling advanced features like offline support, caching, and queueing. Its design, especially when paired with TypeScript, helps you catch errors early and build robust, maintainable data flows.


Purpose
  1. Requests role is to create consistent API request templates.
  2. All requests "templates" are reusable and share the data structure.
  3. They behave in the same way no matter what the adapter is, to improve reusability.
  4. Request class holds instructions on how requests should be sent, queued, retried, or cancelled.
  5. Responses are typed and structured to make it easy to work with the data.

Initialization

Request should be initialized from the Client instance with createRequest method. This passes a shared reference to the place that manages communication in the application.

Example of Request initialization
import { client } from "./client";

const getUser = client.createRequest()({
endpoint: "/users/:userId",
});
Simple fetch
TypeScript Currying Note

The createRequest method currently employs a currying pattern to facilitate auto-generated TypeScript types for endpoint strings. This is a temporary approach until this specific TypeScript issue is resolved, which will allow for a more direct typing mechanism.


Quick Start

Here is a quick start guide to help you get started with Requests.

Fetching

There are two main methods to send a request with Hyper Fetch - send(options) and exec(options).

  1. .send(options)

You can perform a request with the send(options) method. This is the most common way to send a request with Hyper Fetch. It triggers the request's lifecycle, including queueing, deduplication, and response handling, and returns the server's response in a developer-friendly format.

// Simple fetch
const { data, error } = await getNotes.setQueryParams({ sort: "age" }).send();

// Multiple chained methods
const { data, error } = await getCategory.setParams({ categoryId: 2 }).send();

// Passing options
const { data, error } = await getCategory.send({ params: { categoryId: 1 }, queryParams: { sort: "createdAt" } });
  1. .exec(options)

This method is very similar to send but it ignores the built-in features - like for example deduplication or caching. It is useful when you want to execute a request outside of the normal flow or when you want to bypass the cache interactions - for example while using in SSR environments.

const { data, error } = await getUser.exec();
Fetching
Discover how Hyper Fetch handles request execution.

Request Options

Each request can be configured with options. We can specify things like endpoint, method, caching strategy, deduplication, etc. This information is stored as a template for later use.

const request = client.createRequest()({
// Here are some basic options
endpoint: "/some-endpoint",
method: "POST",
deduplicate: true,
});

RequestOptionsType
Name Type Description
abortKey
string
Key which will be used to cancel requests. Autogenerated by default.
auth
boolean
Should the onAuth method get called on this request
cache
boolean
Should we save the response to cache
cacheKey
string
Key which will be used to cache requests. Autogenerated by default.
cacheTime
number
Should we trigger garbage collection or leave data in memory
cancelable
boolean
Should enable cancelable mode in the Dispatcher
deduplicate
boolean
Should we deduplicate two requests made at the same time into one
deduplicateTime
number
Time of pooling for the deduplication to be active (default 10ms)
disableRequestInterceptors
boolean
Disable pre-request interceptors
disableResponseInterceptors
boolean
Disable post-request interceptors
endpoint
GenericEndpoint
Determine the endpoint for request request
headers
HeadersInit
Custom headers for request
method
RequestMethods
Request method picked from method names handled by adapter With default adapter it is GET | POST | PATCH | PUT | DELETE
offline
boolean
Do we want to store request made in offline mode for latter use when we go back online
options
AdapterOptions
Additional options for your adapter, by default XHR options
queryKey
string
Key which will be used to queue requests. Autogenerated by default.
queued
boolean
Should the requests from this request be send one-by-one
retry
number
Retry count when request is failed
retryTime
number
Retry time delay between retries
staleTime
number
Time for which the cache is considered up-to-date


Payload

When you want to send some data along with the request, you can use the setPayload method. This can be a json, FormData or other format - depending on the adapter you use.

First we define request expecting proper type.

const postUser = client.createRequest<{
response: UserModel;
payload: UserPostDataType;
}>()({
method: "POST",
endpoint: "/users",
});

const postFile = client.createRequest<{
payload: FormData;
}>()({
method: "POST",
endpoint: "/files",
});
// Regular data
const response = await postUser.setPayload({ name: "John", age: 18 }).send();

You can also pass payload directly to the send method.

const response = await postFile.send({ payload: data });
Payload
Understand how to send payload data with your requests.

Payload with files

When you want to send a file, you can use the FormData.

// Form data
const data = new FormData();
const file = new File([], "file.txt");
data.append("file", file);

const response = await postFile.setPayload(data).send();
Data Mapping Guide
Discover how to map and transform data effectively.

Parameters

Parameters are the URL parts used to identify a specific resource. In HyperFetch they are defined in the endpoint part with : prefix. For example:

const getNote = client.createRequest()({
endpoint: "/note/:noteId", // noteId is a parameter
});
const getCategory = client.createRequest()({
endpoint: "/category/:categoryId", // categoryId is a parameter
});
const getCategoryNote = client.createRequest()({
endpoint: "/category/:categoryId/note/:noteId", // categoryId and noteId are parameters
});

When you have properly prepared requests that expect parameters, you can add parameters using the setParams method. In generic TypeScript, these parameters will match the endpoint parameters by using literal types and will require literal types.

getNote.setParams({ noteId: 1 });
getCategory.setParams({ categoryId: 2 });
getCategoryNote.setParams({ categoryId: 2, noteId: 1 });

You can also pass parameters directly to the send method.

const response = await getCategoryNote.send({ params: { categoryId: 1, noteId: 2 } });
Parameters
Learn how to define and use route parameters in your requests.

Query parameters

Query parameters are used to filter or sort the data. In Http requests they are defined in the endpoint part with ? prefix. For example ?search=John&sort=age.

We can set query params with the setQueryParams method.

const getUsers = client.createRequest<{
queryParams?: { search?: string; sort?: string };
}>()({
endpoint: "/users",
});
const response = await getUsers.setQueryParams({ search: "John" }).send();

Note that we use ?: prefix to make query params optional. We can make it required as a whole but also particular params from the set.

Required Query Params

You can make query params required by specifying them without ?: prefix.

// All params are required before request is sent
const getUsers = client.createRequest<{
queryParams: { search: string; sort: string };
}>()({
endpoint: "/users",
});

// Typescript error - sort is required
const response = await getUsers.setQueryParams({ search: "John" }).send();

// Typescript error - search is required
const response = await getUsers.setQueryParams({ sort: "age" }).send();

// Typescript error - query params are required
const response = await getUsers.send();
getUsers.setQueryParams({ search: "John", sort: "age" });

The encoding type for arrays and other options can be set up in the Client. You can also provide your own encoding logic.

getUsers.setQueryParams({ search: "John", sort: "age" });
Query Parameters
Explore how to set and manage query parameters for API requests.

Adapter Options

You can pass adapter options down with the request options.

const request = client.createRequest()({
endpoint: "/some-endpoint",
// Here are adapter options for the Http adapter (default one)
options: { timeout: 1000, withCredentials: true },
});
info

Adapter options may vary depending on the adapter you use.


Methods

The Request class offers a suite of methods to configure and interact with a request instance. You can find a comprehensive list of these methods and their detailed descriptions in the API reference.

Methods
Name Description
fromJSON(client, json)
toJSON()
setUsed(used)
setStaleTime(staleTime)
setRetryTime(retryTime)
setRetry(retry)
setResponseMapper(responseMapper)
Map the response to the new interface
setRequestMapper(requestMapper)
Map request before it gets send to the server
setQueued(queued)
setQueryParams(queryParams)
setQueryKey(queryKey)
setPayloadMapper(payloadMapper)
Map data before it gets send to the server
setPayload(payload)
setParams(params)
setOptions(options)
setOffline(offline)
setMockingEnabled(isMockerEnabled)
setMock(fn, config)
setHeaders(headers)
setDeduplicateTime(deduplicateTime)
setDeduplicate(deduplicate)
setCancelable(cancelable)
setCacheTime(cacheTime)
setCacheKey(cacheKey)
setCache(cache)
setAuth(auth)
setAbortKey(abortKey)
read()
Read the response from cache dataIf it returns error and data at the same time, it means that latest request was failed and we show previous data from cache together with error received from actual request
dehydrate(config)
clone(configuration)
clearMock()
abort()
sendMethod used to perform requests with usage of cache and queues
execMethod to use the request WITHOUT adding it to cache and queues. This mean it will make simple request without queue side effects.

Detailed Request API Methods
Explore all available methods, their parameters, and return values for the Request class.

It is crucial to understand how these methods operate to ensure predictable behavior:

Methods Return Clones

Most methods on a Request instance (e.g., setParams, setData, setHeaders) do not modify the original request instance in place. Instead, they return a new, cloned instance of the request with the specified modifications applied. The original request instance remains unchanged.

Always use the returned new instance for subsequent operations or for sending the request. This immutable approach ensures isolation between different request configurations and is fundamental to how Hyper Fetch manages request states for features like caching and queueing.

const initialRequest = getUser;
// ❌ WRONG - Original request (getUser) remains unchanged
initialRequest.setParams({ userId: 1 }); // setParams returns a new request instance
// 'initialRequest' still refers to the original getUser without params.

// Sends the original request without params, likely leading to an error.
const { data, error } = await initialRequest.send();
// ✅ CORRECT - Use the returned (cloned) instance
const originalRequest = getUser;

// Assign the new, cloned request with parameters to a new variable
const requestWithParams = originalRequest.setParams({ userId: 1 });

// Send the correctly configured request
const { data, error } = await requestWithParams.send(); // Success!

// ✅ Also correct (method chaining creates and passes clones implicitly)
const { data: chainedData, error: chainedError } = await getUser
.setParams({ userId: 1 }) // .setParams returns a clone
.send(); // .send operates on the final configured clone

Keys

Each Request instance is assigned several unique identifiers, known as keys. These keys are essential for internal mechanisms—such as caching, queueing, request cancellation, deduplication, effect management and many more. Think of them as "fingerprints" that help Hyper Fetch track, manage, and use for assigning side-effects to requests.

Keys are generated automatically

Keys are generated automatically based on the request's endpoint, method, and parameters. Currently there are two types of the generation mechanisms - simple and complex keys. Complex keys include query params in the generation - simple keys include only endpoint and method.

queryKey

General identifier for the request, used for query management (e.g., refetching, optimistic updates, invalidation).

const key = request.queryKey;

Set custom key with setQueryKey method.

const customRequest = request.setQueryKey("custom-key");
More about queryKey
Learn more about queryKey.

cacheKey

Determines how the response is stored and retrieved from the cache. Ensures correct data is cached and served.

const key = request.cacheKey;

Set custom key with setCacheKey method.

const customRequest = request.setCacheKey("custom-key");
More about cacheKey
Learn more about cacheKey.

abortKey

Identifies and manages ongoing requests for cancellation (e.g., aborting in-flight requests).

const key = request.abortKey;

Set custom key with setAbortKey method.

const customRequest = request.setAbortKey("custom-key");
More about abortKey
Learn more about abortKey.

Generation mechanism

By default, these keys are auto-generated based on the request's endpoint, method, and parameters. This ensures that each unique request configuration is tracked separately. Your can override this mechanism by providing your own implementation.

import { client } from "./api";

client.setCacheKeyMapper((request) => {
if (request.requestOptions.endpoint === "/users/:userId") {
return `CUSTOM_CACHE_KEY_${request.params?.userId || "unknown"}`;
}
});

Why override keys?

In most cases, the default keys are sufficient. However, you may want to override them for advanced scenarios, such as:

  • Custom cache strategies: Grouping multiple endpoints under a single cache key.
  • Manual request deduplication: Treating different requests as identical for deduplication or cancellation.
  • Abort groups: Being able to abort multiple requests at once.

Overriding keys

You can override any key using the corresponding method:

const customRequest = request.setQueryKey("custom-key");
const customCacheRequest = request.setCacheKey("my-cache-key");
const abortableRequest = request.setAbortKey("my-abort-key");
const effectLinkedRequest = request.setEffectKey("my-effect-key");

Note: Overriding keys is an advanced feature. Make sure you understand the implications for caching, deduplication, and request management.


Features

The Request class is packed with features to streamline your data fetching and management. Here are some key capabilities:

  1. Cancellation

Cancellation allows you to abort an in-progress request before it completes. This is useful in scenarios where the result of a request is no longer needed, such as when a user navigates away from a page or changes a filter before the previous request finishes. Aborting a request helps prevent unnecessary processing and side effects from outdated responses.

  1. Manual cancellation

Triggered by the .abort() request method, allow us to stop the request execution.

Manual cancellation
  1. Automatic cancellation

It is automatically executed when we send two identical requests at the same time. Older request will be cancelled and the new one will be sent.

Automatic cancellation
Cancellation Guide
Learn how to cancel requests and manage their lifecycle.

  1. Queueing

Queueing ensures that requests are sent one after another, rather than in parallel. When enabled, each request waits for the previous one to finish before starting. This is important for operations that must be performed in order, or when interacting with APIs that require serialized access.

Queueing
Queueing
Understand how requests are queued and processed in sequence.

  1. Offline Support

Offline support allows requests to be stored when the application is offline. These requests are automatically retried once the network connection is restored. This feature is useful for applications that need to function reliably in environments with intermittent connectivity.

Live results
Offline Guide
Learn how to leverage offline capabilities for robust applications.

  1. Deduplication

Deduplication prevents multiple identical requests from being sent at the same time. If a request is already in progress, additional requests with the same parameters will reuse the ongoing request and share its response. This reduces redundant network traffic and ensures consistent results.

Live results
Deduplication
Learn how to prevent duplicate requests using the deduplication feature.

  1. Authentication

Authentication integrates the request with the client's authentication mechanism. When enabled, authentication tokens or credentials are automatically included with the request. This is necessary for accessing protected resources or APIs that require user identity.

// Add the onAuth interceptor
client.onAuth(({ request }) => {
// Modify request - by adding the auth token to the headers
request.setHeaders({
Authorization: `Bearer ${auth.token}`,
});

return request;
});

// Authenticated request (uses Client's auth setup)
const authRequest = client.createRequest()({ endpoint: "/profile", auth: true });

// it will authenticate the request
authRequest.send();
Authentication Guide
Learn about setting up authentication for your requests via the Client.

  1. Mapping

Response mapping allows you to transform the request payload before sending it, or the response data after receiving it. This is useful for adapting data formats between your application and external APIs, or for preprocessing data for validation or display.

// Map response data to a new structure
const { data: mappedResponse } = await client
.createRequest<{ response: { name: string } }>()({ endpoint: "/user" })
.setResponseMapper((res) => ({ ...res, data: { displayName: res.data.name } }))
.send();

console.log(mappedResponse); // { displayName: "John" }
Response Mapping Guide
Discover how to map and transform data effectively.

Request mapping provides hooks for custom transformations of payloads, requests, and responses. This allows for complex data processing scenarios, such as chaining multiple transformations, integrating with third-party services, or adapting to evolving API contracts.

// Advanced mapping: transform payload before sending
const request = client
.createRequest<{ payload: { name: string } }>()({ endpoint: "/advanced" })
.setPayloadMapper((payload) => ({ ...payload, name: payload.name.toUpperCase() }));

// Will send the request with the payload { name: "JOHN" }
const mappedRequest = request.setPayload({ name: "john" }).send();
Request Mapping Guide
Explore advanced data mapping techniques for complex scenarios.

  1. Retries

Retries automatically resend a request if it fails due to network errors or server issues. You can configure the number of retry attempts and the delay between them. This feature helps improve reliability in the face of transient errors.

// Retry failed requests automatically
const retryRequest = client.createRequest()({ endpoint: "/unstable", retry: 2, retryTime: 1000 });
retryRequest.send(); // Will retry up to 2 times on failure
Retries
Learn how to configure automatic retries for failed requests.

  1. Error handling

Error handling provides mechanisms to detect and respond to errors that occur during the request lifecycle. You can define callbacks for different error types, such as network failures, validation errors, or server responses, allowing your application to handle failures appropriately.

// Handle errors with onError callback
const errorRequest = client.createRequest()({ endpoint: "/fail" });

// Will return error without throwing it
const { error } = await errorRequest.send();

// You can also handle the error with callback
await errorRequest.send({
onError: ({ response }) => {
console.error("Request failed:", response.error);
},
});
Error handling
Understand the error handling mechanisms in Hyper Fetch requests.

  1. Validation

Validation enables you to check the request data before it is sent, and the response data after it is received. This helps ensure that only valid data is processed by your application, and that errors are caught early in the data flow.

// Validate request data before sending
const validatedRequest = client
.createRequest<{ payload: { age: number } }>()({ endpoint: "/validate" })
.setRequestMapper((req) => {
if (req.payload.age < 18) throw new Error("Must be 18+");
return req;
});
const { error } = await validatedRequest.setPayload({ age: 16 }).send();

console.log(error); // Error: Must be 18+
Validation
Learn about validating request data before sending and response data upon receipt.

Lifecycle listeners

The send() method accepts an options object where you can define lifecycle callback functions. These callbacks allow you to hook into various stages of the request's lifecycle—such as onBeforeSent (when the request finishes, regardless of outcome), onSuccess, onError, onStart, onProgress, onAbort, and onOfflineError—to perform actions based on the request's progress and outcome.

await someRequest.send({
onBeforeSent: ({ response, requestId }) => {
console.log(`Request ${requestId} has settled. Final response:`, response);
// Called right before the request is sent—useful for quickly accessing the requestId for tracking or logging
},
onStart: ({ requestId, request }) => {
console.log(`Request ${requestId} has started.`);
// Called when the request is started—useful for showing a loading indicator
},
onUploadProgress: ({ progress, timeLeft, sizeLeft, total, loaded, startTimestamp, request, requestId }) => {
console.log(`Request ${requestId} upload progress:`, progress);
// Called when the upload progress is updated—useful for showing a progress bar
},
onDownloadProgress: ({ progress, timeLeft, sizeLeft, total, loaded, startTimestamp, request, requestId }) => {
console.log(`Request ${requestId} download progress:`, progress);
// Called when the download progress is updated—useful for showing a progress bar
},
onResponse: ({ response, requestId }) => {
console.log(`Request ${requestId} finished with data:`, response.data);
// Called when the request succeeds—useful for processing successful data
},
onRemove: ({ request, requestId }) => {
console.log(`Request ${requestId} removed.`);
// Called when the request is removed—useful for cleaning up
},
});

TypeScript

When creating a request with client.createRequest, you can specify up to four generic types to ensure type safety and enhance the developer experience throughout the request lifecycle:

  • response: The expected type of the successful response data from the server.
  • payload: The type of the data to be sent in the request body.
  • error: The type for errors specific to this request, often used for local validation or business logic errors, complementing the global error type defined on the Client.
  • queryParams: The expected structure and types for the request's query parameters.
const request = client.createRequest<{
response: { name: string };
payload: { age: number };
error: { message: string };
queryParams: { search: string };
}>()({ endpoint: "/user" });

const { data, error } = await request.send();

console.log(data); // { name: "John" }
console.log(error); // undefined
Install Eslint plugin

To ensure type safety, we recommend installing the eslint-plugin-hyper-fetch plugin. It will help you catch type errors early and build robust, maintainable data flows.

Learn how to install and configure the plugin in the Eslint plugin guide.


Generics

Starting from Hyper Fetch v7.0.0, all the types for the Client class and the createRequest method are passed as objects. This way, user can specify only what they want, in any order, in very a readable way.

const client = new Client<{ error: ErrorType }>({ url });

const getUsers = client.createRequest<{ response: ResponseType; queryParams: QueryParamsType }>()({
endpoint: "/users",
});
caution

We firmly believe that it is more readable approach. Yet, it comes at a cost - currently, TypeScript does not handle well the autosuggestions and type inference for the extended object-like generics (https://github.com/microsoft/TypeScript/issues/28662). Fear not - the types are working correctly and we found a way to make it work with our own eslint plugin.

For example, when writing a request, e.g.:

const postUser = client.createRequest<{ invalidGeneric: string }>()({ endpoint: "/" });

TypeScript will not throw an error if you use an invalid key like invalidGeneric that isn't part of our API. To catch these issues early, we recommend using our custom ESLint plugin. This approach combines the benefits of type safety and readability, while ensuring your code uses the correct API syntax.