Switch to Rezo

From Got

Got is a well-designed HTTP library for Node.js with hooks, retry, and pagination. If you have been productive with Got, you will feel at home with Rezo — the concepts are similar. The difference is scope: Rezo runs on every JavaScript runtime, and ships with features Got does not offer: stealth mode, proxy rotation, a web crawler, site cloning, and a cURL adapter.

Basic Requests

// Got
import got from 'got';

const { body } = await got('https://api.example.com/users').json();

// Rezo -- parsed JSON in response.data
import rezo from 'rezo';

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

Instance Creation

// Got
const client = got.extend({
  prefixUrl: 'https://api.example.com',
  timeout: { request: 5000 },
  headers: { 'X-API-Key': 'abc123' },
});

const { body } = await client.get('users').json();

// Rezo
import rezo from 'rezo';

const client = rezo.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,           // single number covers the whole request
  headers: { 'X-API-Key': 'abc123' },
});

const { data } = await client.get('/users');

POST with JSON

// Got
const { body } = await got.post('https://api.example.com/users', {
  json: { name: 'Ada Lovelace' },
}).json();

// Rezo
const { data } = await rezo.postJson('https://api.example.com/users', {
  name: 'Ada Lovelace',
});

Hooks

Got provides 9 hook points. Rezo provides 26, covering every phase from DNS resolution to cookie processing.

// Got
const client = got.extend({
  hooks: {
    beforeRequest: [(options) => { /* ... */ }],
    afterResponse: [(response) => { return response; }],
    beforeRetry: [(error, retryCount) => { /* ... */ }],
    beforeRedirect: [(options, response) => { /* ... */ }],
    beforeError: [(error) => { return error; }],
    init: [(plain, options) => { /* ... */ }],
  }
});

// Rezo -- same concept, more hook points
const client = rezo.create({
  hooks: {
    // Core lifecycle (Got-equivalent)
    beforeRequest:  [(config, context) => { /* ... */ }],
    afterResponse:  [(response, config, context) => { return response; }],
    beforeRetry:    [(config, error, retryCount) => { /* ... */ }],
    beforeRedirect: [(context, config, response) => { /* ... */ }],
    beforeError:    [(error) => { return error; }],
    init:           [(plainOptions, options) => { /* ... */ }],

    // Rezo-only hooks
    beforeCache:    [(event) => { /* return false to skip caching */ }],
    afterHeaders:   [(event, config) => { /* ... */ }],
    afterParse:     [(event, config) => { return event.data; }],
    beforeCookie:   [(event, config) => { /* return false to reject cookie */ }],
    afterCookie:    [(cookies, config) => { /* ... */ }],

    // Low-level network events (fire-and-forget)
    onDns:     [(event, config) => { /* DNS resolution metrics */ }],
    onSocket:  [(event, socket) => { /* TCP socket lifecycle */ }],
    onTls:     [(event, socket) => { /* TLS handshake info */ }],
    onTimeout: [(event, config) => { /* timeout fired */ }],
    onAbort:   [(event, config) => { /* request aborted */ }],

    // Rate-limit waiting
    onRateLimitWait: [(event, config) => { /* about to wait on 429 */ }],

    // Proxy lifecycle (when proxyManager is configured)
    beforeProxySelect:    [(ctx) => { /* ... */ }],
    afterProxySelect:     [(ctx) => { /* ... */ }],
    afterProxyError:      [(ctx) => { /* ... */ }],
    afterProxyDisable:    [(ctx) => { /* ... */ }],
    afterProxyRotate:     [(ctx) => { /* ... */ }],
    onNoProxiesAvailable: [(ctx) => { /* ... */ }],
  }
});

Retry

Both libraries support retry with backoff. Rezo adds custom status codes and condition functions.

// Got
const client = got.extend({
  retry: {
    limit: 3,
    methods: ['GET', 'POST'],
    statusCodes: [408, 413, 429, 500, 502, 503, 504],
    calculateDelay: ({ attemptCount }) => attemptCount * 1000,
  }
});

// Rezo
const client = rezo.create({
  retry: {
    limit: 3,
    methods: ['GET', 'POST'],
    statusCodes: [408, 413, 429, 500, 502, 503, 504],
    delay: 1000,
    maxDelay: 10_000,
    backoff: 'exponential',
    condition: (error, attempt) => {
      // Custom logic beyond status codes
      return !error.response || error.response.status !== 401;
    },
  }
});

Pagination

Both libraries support pagination. Rezo uses an async generator pattern.

// Got
const results = await got.paginate.all('https://api.example.com/items', {
  pagination: {
    transform: (response) => JSON.parse(response.body),
    paginate: ({ response, currentItems }) => {
      const next = response.headers.link?.match(/<(.+?)>; rel="next"/)?.[1];
      if (!next) return false;
      return { url: next };
    },
  }
});

// Rezo -- async generator
const client = rezo.create();

const allItems = [];
for await (const items of client.paginate('https://api.example.com/items', {
  pagination: {
    // Optional — Link header `rel="next"` is auto-detected if you omit this
    getNextUrl: (response) => {
      const link = response.headers.get('link');
      return link?.match(/<(.+?)>; rel="next"/)?.[1] || null;
    },
    transform: (response) => response.data,
  }
})) {
  allItems.push(...items);
}

Timeouts

// Got -- partial staged timeouts
const client = got.extend({
  timeout: {
    lookup: 1000,
    connect: 3000,
    secureConnect: 3000,
    socket: 5000,
    send: 10000,
    response: 10000,
    request: 30000,
  }
});

// Rezo -- single number for the whole request, or staged budgets per phase
const client = rezo.create({
  timeout: 60_000
});

// Or staged via StagedTimeoutConfig:
const client2 = rezo.create({
  timeout: {
    connect: 5_000,
    headers: 10_000,
    body: 30_000,
    total: 60_000,
  }
});

Error Handling

// Got
import got, { HTTPError, TimeoutError } from 'got';

try {
  await got('https://api.example.com/data');
} catch (error) {
  if (error instanceof HTTPError) {
    console.log(error.response.statusCode);
  } else if (error instanceof TimeoutError) {
    console.log('Request timed out');
  }
}

// Rezo -- single error class with boolean flags
import rezo, { RezoError } from 'rezo';

try {
  await rezo.get('https://api.example.com/data');
} catch (error) {
  if (rezo.isRezoError(error)) {
    console.log(error.code);          // "REZ_HTTP_ERROR" or "ETIMEDOUT"
    console.log(error.status);        // 404
    console.log(error.isTimeout);     // false
    console.log(error.isHttpError);   // true (any non-2xx response)
    console.log(error.isRetryable);   // true if safe to retry
    console.log(error.suggestion);    // recovery advice
  }
}

Downloads

// Got
import { pipeline } from 'node:stream/promises';
import { createWriteStream } from 'node:fs';

const downloadStream = got.stream('https://example.com/file.zip');
downloadStream.on('downloadProgress', ({ transferred, total, percent }) => {
  console.log(`${(percent * 100).toFixed(1)}%`);
});
await pipeline(downloadStream, createWriteStream('./file.zip'));

// Rezo — download() returns a RezoDownloadResponse emitter
const download = rezo.download('https://example.com/file.zip', './file.zip');

download.on('progress', ({ percentage, loaded, total }) => {
  console.log(`${percentage}%`);
});

download.on('finish', (info) => {
  console.log('Saved to', info.fileName, info.fileSize, 'bytes');
});

What You Gain by Switching

Multi-Runtime Support

Got is Node.js-only. Rezo runs on Node.js, Bun, Deno, browsers, React Native, and edge runtimes with the same API.

Stealth Mode

import rezo, { RezoStealth } from 'rezo';

const client = rezo.create({
  stealth: new RezoStealth({ family: 'chrome', rotate: true }),
});

18 browser profiles with TLS fingerprinting, HTTP/2 SETTINGS emulation, header ordering, and client hints.

Proxy Rotation

import rezo, { ProxyManager } from 'rezo';

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

Automatic rotation with health monitoring. Got requires manual agent setup for each proxy type.

import rezo, { CookieJar } from 'rezo';

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

// Cookies managed automatically -- export to JSON or Netscape
jar.toJSON();
jar.toNetscapeCookie();

Got requires tough-cookie as a separate dependency.

Web Crawler

import { Crawler } from 'rezo/crawler';

const crawler = new Crawler({
  baseUrl: 'https://example.com',
  maxDepth: 3,
  concurrency: 10,
});

crawler.onDocument(async (document) => {
  console.log(document.location?.href);
});

await crawler.visit('https://example.com');
await crawler.done();

Queue-based crawling with robots.txt compliance, SQLite persistence, and memory monitoring. Nothing comparable in Got.

Site Cloning

import { Wget } from 'rezo/wget';

await new Wget({
  recursive: { enabled: true, depth: 5, convertLinks: true, pageRequisites: true },
  download: { outputDir: './mirror' }
}).get('https://docs.example.com');

cURL Adapter

// The adapter entry exports a default rezo instance pre-wired with cURL
import rezo from 'rezo/adapters/curl';

const client = rezo.create({ baseURL: 'https://api.example.com' });

Useful for TLS fingerprint impersonation and debugging. Not available in Got.

Feature Comparison

FeatureRezoGot
Node.jsYesYes
BrowsersYesNo
Deno / Edge / React NativeYesNo
Hooks269
Retry with backoffYesYes
PaginationYes (async generator)Yes
TimeoutsStaged (4 phases)Staged (6 phases)
Cookie jarBuilt-inPlugin (tough-cookie)
Cookie persistenceJSON and NetscapeNo
Proxy rotationBuilt-inNo
SOCKS proxyBuilt-inNo
Stealth mode18 profilesNo
Web crawlerBuilt-inNo
Site cloningBuilt-inNo
cURL adapterYesNo
HTTP/2YesYes
Downloads with progressYesYes
Request queueBuilt-inNo
Response cachingBuilt-inPlugin
DNS cachingBuilt-inPlugin
Error codes70+~12
Recovery suggestionsYesNo