
Research
/Security News
737 Chrome VPN Extensions Linked to Brand Impersonation and Browser Traffic Redirection
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.
@applitools/req
Advanced tools
A powerful, flexible HTTP request library with advanced features like retry logic, hooks, fallbacks, and timeout management.
yarn add @applitools/req
import {req} from '@applitools/req'
// Simple GET request
const response = await req('https://api.example.com/data')
const data = await response.json()
The main function for making HTTP requests.
Signature:
req(input: string | URL | Request, ...options: Options[]): Promise<Response>
Parameters:
input - URL string, URL object, or Request objectoptions - One or more option objects that will be merged (optional)Returns: Promise that resolves to a Response object
Creates a req function with predefined base options.
Signature:
makeReq<TOptions>(baseOptions: Partial<TOptions>): Req<TOptions>
Example:
const apiReq = makeReq({
baseUrl: 'https://api.example.com',
headers: {'Authorization': 'Bearer token123'}
})
// All requests will use the base options
const response = await apiReq('/users')
All available options for configuring requests:
baseUrlType: string
Base URL for relative paths. Automatically adds trailing slash if missing.
Example:
await req('./users', {baseUrl: 'https://api.example.com/v1'})
// Makes request to: https://api.example.com/v1/users
methodType: string
HTTP method (uppercase). Overrides method from Request object.
Example:
await req('https://api.example.com/users', {method: 'POST'})
queryType: Record<string, string | boolean | number | undefined>
Query parameters to append to URL. Merges with existing query params. Undefined values are ignored.
Example:
await req('https://api.example.com/search?page=1', {
query: {
limit: 10,
filter: 'active',
skip: undefined // This won't be added
}
})
// URL: https://api.example.com/search?page=1&limit=10&filter=active
headersType: Record<string, string | string[] | undefined>
HTTP headers. Merges with headers from Request object. Undefined values are filtered out.
Example:
await req('https://api.example.com/data', {
headers: {
'Authorization': 'Bearer token',
'Content-Type': 'application/json',
'X-Optional': undefined // Won't be sent
}
})
bodyType: NodeJS.ReadableStream | ArrayBufferView | string | Record<string, any> | any[] | null
Request body. Plain objects and arrays are automatically serialized to JSON with appropriate content-type header.
Example:
// Automatic JSON serialization
await req('https://api.example.com/users', {
method: 'POST',
body: {name: 'John', age: 30}
})
// Binary data
await req('https://api.example.com/upload', {
method: 'POST',
body: Buffer.from('binary data')
})
proxyType: Proxy | ((url: URL) => Proxy | undefined)
Proxy configuration. Can be an object or function that returns proxy settings based on URL.
Example:
// Static proxy
await req('https://api.example.com/data', {
proxy: {
url: 'http://proxy.company.com:8080',
username: 'user',
password: 'pass'
}
})
// Dynamic proxy
await req('https://api.example.com/data', {
proxy: (url) => {
if (url.hostname.includes('internal')) {
return {url: 'http://internal-proxy:8080'}
}
}
})
useDnsCacheType: boolean
Enable DNS caching for improved performance.
Example:
await req('https://api.example.com/data', {useDnsCache: true})
connectionTimeoutType: number
Total timeout in milliseconds for the entire connection including all retries. Once exceeded, throws ConnectionTimeoutError.
Example:
await req('https://api.example.com/data', {
connectionTimeout: 30000, // 30 seconds total
retry: {statuses: [500]}
})
requestTimeoutType: number | {base: number; perByte: number}
Timeout for a single request in milliseconds. Can be dynamic based on request body size.
Example:
// Fixed timeout
await req('https://api.example.com/data', {
requestTimeout: 5000 // 5 seconds per request
})
// Dynamic timeout based on body size
await req('https://api.example.com/upload', {
method: 'POST',
body: largeBuffer,
requestTimeout: {
base: 1000, // 1 second base
perByte: 0.001 // + 1ms per byte
}
})
retryTimeoutType: number
Maximum duration in milliseconds for all retry attempts across all retry strategies. Once exceeded, throws RetryTimeoutError.
Example:
await req('https://api.example.com/data', {
retryTimeout: 30000, // Stop all retries after 30 seconds total
retry: [
{statuses: [500], timeout: 1000},
{codes: ['ECONNRESET'], timeout: 500}
]
})
retryType: Retry | Retry[]
Retry configuration(s). Multiple retry configs can be provided as array.
See Retry Logic for detailed examples.
hooksType: Hooks | Hooks[]
Lifecycle hooks for request interception and modification.
See Hooks for detailed examples.
fallbacksType: Fallback | Fallback[]
Fallback strategies for handling failures.
See Fallbacks for detailed examples.
keepAliveOptionsType: {keepAlive: boolean; keepAliveMsecs?: number}
HTTP agent keep-alive configuration.
Example:
await req('https://api.example.com/data', {
keepAliveOptions: {
keepAlive: true,
keepAliveMsecs: 1000
}
})
signalType: AbortSignal
Abort signal for canceling requests.
Example:
const controller = new AbortController()
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000)
await req('https://api.example.com/data', {
signal: controller.signal
})
The retry option enables automatic retrying of failed requests based on various conditions.
interface Retry {
limit?: number // Max retry attempts (default: unlimited if undefined)
timeout?: number | number[] // Delay between retries in ms (default: 0 - no delay)
statuses?: number[] // HTTP status codes to retry (default: none)
codes?: string[] // Error codes to retry (default: none)
validate?: (options) => boolean // Custom validation function (default: undefined)
}
Default Behavior:
limit: If undefined or not set, retries will continue indefinitely (use with caution - combine with connectionTimeout or retryTimeout)timeout: If undefined, retries happen immediately with no delay (0ms)statuses: If undefined, no status codes trigger retries (must be explicitly configured)codes: If undefined, no error codes trigger retries (must be explicitly configured)validate: If undefined, only statuses and codes are checkedstatuses, codes, or validate must match for a retry to occurRetry-After header: If present in the response, overrides the configured timeout valueRetry on specific status codes:
await req('https://api.example.com/data', {
retry: {
statuses: [500, 502, 503], // Retry on server errors
limit: 3, // Max 3 attempts
timeout: 1000 // Wait 1 second between retries
}
})
Retry on network errors:
await req('https://api.example.com/data', {
retry: {
codes: ['ECONNRESET', 'ETIMEDOUT'],
limit: 5,
timeout: 2000
}
})
Exponential backoff:
await req('https://api.example.com/data', {
retry: {
statuses: [429, 500],
limit: 4,
timeout: [1000, 2000, 4000, 8000] // Double delay each time
}
})
Custom validation:
await req('https://api.example.com/data', {
retry: {
validate: async ({response, error}) => {
if (error) return true
if (response?.status === 429) {
// Check rate limit header
return response.headers.has('Retry-After')
}
return false
},
limit: 3
}
})
Multiple retry strategies:
await req('https://api.example.com/data', {
retry: [
// Retry network errors quickly
{codes: ['ECONNRESET'], limit: 3, timeout: 500},
// Retry server errors with longer delay
{statuses: [500, 503], limit: 2, timeout: 2000}
]
})
Retry with timeout limit:
await req('https://api.example.com/data', {
retryTimeout: 10000, // Stop all retries after 10 seconds
retry: [
{codes: ['ECONNRESET'], timeout: 500},
{statuses: [500, 503], timeout: 1000}
]
})
// If retries take longer than 10 seconds total, RetryTimeoutError is thrown
Hooks allow you to intercept and modify requests/responses at various lifecycle stages.
interface Hooks {
afterOptionsMerged?(options): TOptions | void
beforeRequest?(options): Request | void
beforeRetry?(options): Request | Stop | void
afterResponse?(options): Response | void
afterError?(options): Error | void
unknownBodyType?(options): void
}
Add authentication header:
await req('https://api.example.com/data', {
hooks: {
beforeRequest: ({request}) => {
request.headers.set('Authorization', `Bearer ${getToken()}`)
}
}
})
Log all requests:
await req('https://api.example.com/data', {
hooks: {
beforeRequest: ({request}) => {
console.log(`${request.method} ${request.url}`)
},
afterResponse: ({response}) => {
console.log(`Response: ${response.status}`)
}
}
})
Conditional retry prevention:
import {stop} from '@applitools/req'
await req('https://api.example.com/data', {
retry: {statuses: [500]},
hooks: {
beforeRetry: async ({response, attempt, stop}) => {
const data = await response?.json()
if (data?.error === 'FATAL') {
console.log('Fatal error, stopping retries')
return stop
}
console.log(`Retry attempt ${attempt}`)
}
}
})
Transform response:
await req('https://api.example.com/data', {
hooks: {
afterResponse: async ({response}) => {
if (!response.ok) {
const error = await response.text()
throw new Error(`API Error: ${error}`)
}
}
}
})
Modify request before retry:
await req('https://api.example.com/data', {
retry: {statuses: [401]},
hooks: {
beforeRetry: async ({request, attempt}) => {
// Refresh token on 401
const newToken = await refreshAuthToken()
request.headers.set('Authorization', `Bearer ${newToken}`)
return request
}
}
})
Multiple hooks:
await req('https://api.example.com/data', {
hooks: [
{
beforeRequest: ({request}) => {
request.headers.set('X-Request-ID', generateId())
}
},
{
beforeRequest: ({request}) => {
request.headers.set('X-Timestamp', Date.now().toString())
}
}
]
})
Fallbacks provide alternative strategies when requests fail.
interface Fallback {
shouldFallbackCondition: (options) => boolean | Promise<boolean>
updateOptions?: (options) => Options | Promise<Options>
cache?: Map<string, boolean>
}
Fallback to different endpoint:
await req('https://api-primary.example.com/data', {
fallbacks: {
shouldFallbackCondition: ({response}) => response.status >= 500,
updateOptions: ({options}) => ({
...options,
baseUrl: 'https://api-backup.example.com'
})
}
})
Try with authentication if unauthorized:
await req('https://api.example.com/data', {
fallbacks: {
shouldFallbackCondition: ({response}) => response.status === 401,
updateOptions: async ({options}) => ({
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${await getToken()}`
}
})
}
})
Multiple fallback strategies:
await req('https://api.example.com/data', {
fallbacks: [
// First try: enable keep-alive
{
shouldFallbackCondition: ({response}) => response.status === 503,
updateOptions: ({options}) => ({
...options,
keepAliveOptions: {keepAlive: true}
})
},
// Second try: use proxy
{
shouldFallbackCondition: ({response}) => response.status === 403,
updateOptions: ({options}) => ({
...options,
proxy: {url: 'http://proxy.example.com:8080'}
})
}
]
})
Three types of timeouts control request timing:
connectionTimeoutTotal timeout across all retries and delays. Throws ConnectionTimeoutError when exceeded.
await req('https://api.example.com/data', {
connectionTimeout: 30000, // 30 seconds total for all attempts
retry: {
statuses: [500],
limit: 5,
timeout: 2000
}
})
// If all 5 retries take too long, ConnectionTimeoutError is thrown
requestTimeoutTimeout for each individual request attempt. Throws RequestTimeoutError when exceeded.
await req('https://api.example.com/data', {
requestTimeout: 5000, // Each attempt times out after 5 seconds
retry: {
codes: ['RequestTimeout'],
limit: 3
}
})
retryTimeoutTotal timeout for all retry attempts across all retry strategies. Throws RetryTimeoutError when exceeded.
await req('https://api.example.com/data', {
retryTimeout: 15000, // Stop retrying after 15 seconds total
retry: [
{codes: ['ECONNRESET'], timeout: 1000},
{statuses: [500, 503], timeout: 2000}
]
})
// If retry process takes longer than 15 seconds, RetryTimeoutError is thrown
Difference between timeouts:
connectionTimeout: Covers the entire request lifecycle including initial attempt and all retriesretryTimeout: Only covers retry attempts (excludes the initial request)requestTimeout: Applies to each individual request attemptawait req('https://api.example.com/upload', {
method: 'POST',
body: fileBuffer,
requestTimeout: {
base: 5000, // 5 seconds baseline
perByte: 0.01 // + 10ms per byte
}
})
// For 1MB file: timeout = 5000 + (1048576 * 0.01) ≈ 15.5 seconds
Multiple option objects can be passed and will be deeply merged. This enables powerful composition patterns.
query, headers) are mergedretry, hooks, fallbacks) are concatenatedBasic chaining:
const baseOptions = {
baseUrl: 'https://api.example.com',
headers: {'User-Agent': 'MyApp/1.0'}
}
const authOptions = {
headers: {'Authorization': 'Bearer token'}
}
// Merged result:
// - baseUrl: 'https://api.example.com'
// - headers: {'User-Agent': 'MyApp/1.0', 'Authorization': 'Bearer token'}
await req('/users', baseOptions, authOptions)
Override with precedence:
const options1 = {
requestTimeout: 5000,
headers: {'X-Version': '1'}
}
const options2 = {
requestTimeout: 10000,
headers: {'X-Version': '2', 'X-New': 'value'}
}
// Result:
// - requestTimeout: 10000 (overridden)
// - headers: {'X-Version': '2', 'X-New': 'value'} (merged)
await req('https://api.example.com/data', options1, options2)
Combining retry strategies:
const networkRetry = {
retry: {codes: ['ECONNRESET'], limit: 3, timeout: 500}
}
const serverRetry = {
retry: {statuses: [500, 503], limit: 2, timeout: 2000}
}
// Both retry strategies will be active
await req('https://api.example.com/data', networkRetry, serverRetry)
Accumulating hooks:
const loggingHooks = {
hooks: {
beforeRequest: ({request}) => console.log('Request:', request.url)
}
}
const authHooks = {
hooks: {
beforeRequest: ({request}) => request.headers.set('Auth', 'token')
}
}
// Both hooks execute in order
await req('https://api.example.com/data', loggingHooks, authHooks)
Practical composition pattern:
// Define reusable option sets
const apiDefaults = {
baseUrl: 'https://api.example.com',
connectionTimeout: 30000,
retry: {codes: ['ECONNRESET'], limit: 3}
}
const withAuth = {
headers: {'Authorization': `Bearer ${token}`}
}
const withRetry = {
retry: {statuses: [500, 502, 503], limit: 5, timeout: [1000, 2000, 4000]}
}
const withLogging = {
hooks: {
beforeRequest: ({request}) => logger.info('Request', request.url),
afterResponse: ({response}) => logger.info('Response', response.status)
}
}
// Compose as needed
await req('/users', apiDefaults, withAuth)
await req('/critical-data', apiDefaults, withAuth, withRetry, withLogging)
Using makeReq for composition:
// Create base client
const apiClient = makeReq({
baseUrl: 'https://api.example.com',
headers: {'User-Agent': 'MyApp/1.0'},
retry: {codes: ['ECONNRESET']}
})
// Add authentication per request
await apiClient('/public-data')
await apiClient('/private-data', {
headers: {'Authorization': 'Bearer token'}
})
// Create specialized client with additional options
const authedClient = makeReq({
baseUrl: 'https://api.example.com',
headers: {
'User-Agent': 'MyApp/1.0',
'Authorization': 'Bearer token'
}
})
await authedClient('/user/profile')
import {req, makeReq, stop} from '@applitools/req'
// Create API client with defaults
const apiClient = makeReq({
baseUrl: 'https://api.example.com/v1',
connectionTimeout: 60000,
requestTimeout: 10000,
headers: {
'User-Agent': 'MyApp/2.0',
'Accept': 'application/json'
},
retry: [
// Quick retry for network errors
{
codes: ['ECONNRESET', 'ETIMEDOUT'],
limit: 3,
timeout: 500
},
// Slower retry for server errors
{
statuses: [500, 502, 503],
limit: 5,
timeout: [1000, 2000, 4000, 8000]
}
],
hooks: {
beforeRequest: ({request}) => {
console.log(`→ ${request.method} ${request.url}`)
},
afterResponse: ({response}) => {
console.log(`← ${response.status} ${response.statusText}`)
},
afterError: ({error}) => {
console.error('Request failed:', error.message)
}
}
})
// Authenticated requests
const authedClient = makeReq({
baseUrl: 'https://api.example.com/v1',
headers: {'Authorization': `Bearer ${getToken()}`},
retry: {
statuses: [401],
limit: 1
},
hooks: {
beforeRetry: async ({request, response, stop}) => {
if (response?.status === 401) {
try {
const newToken = await refreshToken()
request.headers.set('Authorization', `Bearer ${newToken}`)
return request
} catch {
return stop
}
}
}
}
})
// Usage
const users = await apiClient('/users', {
query: {page: 1, limit: 20}
})
const profile = await authedClient('/user/profile')
const result = await authedClient('/user/update', {
method: 'POST',
body: {name: 'John Doe', email: 'john@example.com'}
})
MIT
FAQs
Applitools fetch-based request library
The npm package @applitools/req receives a total of 149,143 weekly downloads. As such, @applitools/req popularity was classified as popular.
We found that @applitools/req demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 48 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.