Switch to Rezo

From Axios

If you know Axios, you already know Rezo. The API patterns are nearly identical — method signatures, interceptors, config merging, and error handling all work the same way. The difference is what ships out of the box: cookies, proxies, stealth, hooks, streaming, HTTP/2, and support for every JavaScript runtime.

Import Changes

// Axios
import axios from 'axios';

// Rezo -- same default export pattern
import rezo from 'rezo';

Instance Creation

// Axios
const client = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
  headers: { 'X-API-Key': 'abc123' },
});

// Rezo -- same pattern
const client = rezo.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
  headers: { 'X-API-Key': 'abc123' },
});

timeout covers the entire request from connection to response, just like Axios. Rezo also supports per-status rate-limit waiting (waitOnStatus) and a richer retry config (see Retry).

HTTP Methods

Every method you use in Axios works identically in Rezo:

// These are the same in both libraries
await client.get('/users');
await client.post('/users', { name: 'Ada' });
await client.put('/users/1', { name: 'Ada Lovelace' });
await client.patch('/users/1', { email: 'ada@example.com' });
await client.delete('/users/1');
await client.head('/users');
await client.options('/users');

Rezo also adds convenience methods for common content types:

// JSON with automatic Content-Type
await client.postJson('/users', { name: 'Ada' });
await client.putJson('/users/1', { name: 'Ada Lovelace' });
await client.patchJson('/users/1', { email: 'ada@example.com' });

// URL-encoded form
await client.postForm('/login', { user: 'ada', pass: 'secret' });

Interceptors

The interceptor API is identical:

// Axios
axios.interceptors.request.use(
  (config) => {
    config.headers.Authorization = `Bearer ${getToken()}`;
    return config;
  },
  (error) => Promise.reject(error)
);

axios.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      return refreshAndRetry(error.config);
    }
    return Promise.reject(error);
  }
);

// Rezo -- same API, on the default instance
rezo.interceptors.request.use(
  (config) => {
    config.headers.set('Authorization', `Bearer ${getToken()}`);
    return config;
  },
  (error) => Promise.reject(error)
);

rezo.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.status === 401) {
      return refreshAndRetry(error.config);
    }
    return Promise.reject(error);
  }
);

Rezo’s interceptors also support a runWhen predicate:

rezo.interceptors.request.use(
  (config) => { /* ... */ return config; },
  null,
  { runWhen: (config) => config.url.startsWith('/api/') }
);

Error Handling

Axios errors carry response, request, and code. Rezo carries the same information plus boolean flags and recovery suggestions.

// Axios
try {
  await axios.get('/api/data');
} catch (error) {
  if (error.response) {
    console.log(error.response.status);
    console.log(error.response.data);
  } else if (error.request) {
    console.log('No response received');
  } else {
    console.log(error.message);
  }
}

// Rezo

try {
  await rezo.get('/api/data');
} catch (error) {
  if (rezo.isRezoError(error)) {
    console.log(error.code);          // e.g. "REZ_HTTP_ERROR"
    console.log(error.status);        // 404
    console.log(error.response?.data);

    // Boolean flags — categorize without parsing status codes by hand
    console.log(error.isHttpError);     // true for any non-2xx response
    console.log(error.isNetworkError);  // ECONNREFUSED, ECONNRESET, ENOTFOUND, …
    console.log(error.isTimeout);       // ETIMEDOUT and friends
    console.log(error.isAborted);       // ABORT_ERR
    console.log(error.isProxyError);    // proxy failed
    console.log(error.isSocksError);    // SOCKS specifically
    console.log(error.isTlsError);      // cert / handshake failure
    console.log(error.isRetryable);     // safe to retry

    // Recovery suggestion — human-readable hint per error code
    console.log(error.suggestion);
  }
}

Query Parameters

// Axios
await axios.get('/users', {
  params: { page: 2, limit: 25 },
  paramsSerializer: (params) => qs.stringify(params),
});

// Rezo -- same pattern, custom serializer optional
await rezo.get('/users', {
  params: { page: 2, limit: 25 },
});

Typed Responses

// Axios
const { data } = await axios.get<User[]>('/users');

// Rezo -- identical
const { data } = await rezo.get<User[]>('/users');

Cancel Requests

// Axios (modern)
const controller = new AbortController();
axios.get('/slow', { signal: controller.signal });
controller.abort();

// Rezo -- same AbortController pattern
const controller = new AbortController();
rezo.get('/slow', { signal: controller.signal });
controller.abort();

Axios Compatibility Helpers

Rezo re-exports the legacy Axios names so existing code that uses axios.Cancel / axios.all / etc. keeps working with a single import swap:

import rezo, {
  Cancel,         // = RezoError (Axios called its error class Cancel)
  CancelToken,    // = AbortController (the modern equivalent of Axios's CancelToken)
  isCancel,       // = (err) => err.code === 'ECONNABORTED'
  isRezoError,    // = RezoError.isRezoError — replaces axios.isAxiosError
  all,            // = Promise.all.bind(Promise)
  spread,         // = (fn) => (arr) => fn(...arr)
} from 'rezo';

// Old Axios pattern, ported as-is:
const [users, posts] = await all([
  rezo.get('/users'),
  rezo.get('/posts'),
]).then(spread((u, p) => [u.data, p.data]));

// Cancellation — use AbortController directly; CancelToken is just an alias.
const controller = new CancelToken();
rezo.get('/slow', { signal: controller.signal });
controller.abort();

If you’re writing new code, prefer Promise.all, AbortController, and RezoError.isRezoError(err) directly — the helpers exist so a search-and-replace migration from Axios doesn’t break.

What You Gain by Switching

These are features Axios does not provide or requires third-party plugins to achieve.

Axios needs axios-cookiejar-support and tough-cookie to handle cookies. Rezo has a cookie jar built into every instance — no setup needed.

// Axios -- requires two extra packages
import axios from 'axios';
import { wrapper } from 'axios-cookiejar-support';
import { CookieJar } from 'tough-cookie';

const jar = new CookieJar();
const client = wrapper(axios.create({ jar }));

// Rezo -- cookies work automatically
const { data } = await rezo.get('https://example.com/login');
// Cookies from the response are stored and sent on subsequent requests
const { data: profile } = await rezo.get('https://example.com/profile');

If you need external control over the jar (exporting, importing, inspecting), you can swap the default instance’s jar in place — or, if you want isolation from the default, build a configured instance:

import rezo, { CookieJar } from 'rezo';

const jar = new CookieJar();

// App-wide: use this jar for every request from the default instance
rezo.jar = jar;

// Or isolated: a separate client whose cookies don't bleed into `rezo`
const client = rezo.create({ jar });

Proxy Manager

const client = rezo.create({
  proxyManager: {
    rotation: 'random',
    proxies: ['http://proxy1:8080', 'socks5://proxy2:1080'],
    autoDisableDeadProxies: true
  }
});

Stealth Mode

import { RezoStealth } from 'rezo';

const client = rezo.create({
  stealth: RezoStealth.chrome()
});

26 Lifecycle Hooks

Beyond interceptors, Rezo provides hooks for DNS resolution, TCP connect, TLS handshake, redirects, retries, cookies, and more. Like interceptors, hooks live on the default instance — push directly into the array:

rezo.hooks.beforeRequest.push((config) => { /* ... */ });
rezo.hooks.afterResponse.push((response, config) => { return response; });
rezo.hooks.beforeRetry.push((config, error, retryCount) => { /* ... */ });
rezo.hooks.onDns.push((event) => { /* DNS metrics */ });

Or pass them at instance construction when you want a scoped configuration:

const client = rezo.create({
  hooks: {
    beforeRequest: [(config) => { /* ... */ }],
    afterResponse: [(response, config) => { return response; }],
  }
});

Streaming and Downloads

const download = rezo.download(
  'https://example.com/large-file.zip',
  './downloads/file.zip'
);
download.on('progress', (p) => console.log(`${p.percentage}%`));
download.on('finish', (info) => console.log('Saved to', info.fileName));

HTTP/2

import rezo from 'rezo/adapters/http2';

const { data } = await rezo.get('https://api.example.com');

cURL Adapter

import rezo from 'rezo/adapters/curl';

const { data } = await rezo.get('https://api.example.com');

Staged Timeouts

A single timeout: number covers the whole request, just like Axios. Pass a StagedTimeoutConfig object to budget each phase independently:

// Per request — no instance needed
await rezo.get('https://api.example.com/slow', {
  timeout: {
    connect: 5_000,
    headers: 10_000,
    body: 30_000,
    total: 60_000,
  }
});

Quick Migration Checklist

  1. Replace import axios from 'axios' with import rezo from 'rezo'
  2. Replace axios.create() with rezo.create()
  3. Replace error.response.status with error.status
  4. Replace header string assignments with config.headers.set() in interceptors
  5. Remove axios-cookiejar-support — cookies are built in
  6. Remove any retry plugins — use retry: { limit: 3 } in config
  7. Enjoy everything else that comes for free