Getting Started

Quick Start

This guide walks through the core features of Rezo: making requests, sending data, handling errors, managing cookies, and working with streams and downloads.

Making a GET Request

The default import is a pre-configured instance ready to use:

import rezo from 'rezo';

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

console.log(response.status);      // 200
console.log(response.statusText);  // 'OK'
console.log(response.data);        // parsed response body
console.log(response.headers);     // RezoHeaders instance
console.log(response.finalUrl);    // final URL after any redirects
console.log(response.cookies);     // cookies from the response

Query Parameters

Pass query parameters via the params option:

const response = await rezo.get('https://api.example.com/users', {
  params: { page: 2, limit: 25, sort: 'name' }
});
// Requests: https://api.example.com/users?page=2&limit=25&sort=name

Typed Responses

Use generics to type the response data:

interface User {
  id: number;
  name: string;
  email: string;
}

const { data } = await rezo.get<User[]>('https://api.example.com/users');
// data is User[]

POST with JSON

Use postJson to send a JSON payload with the correct Content-Type header set automatically:

const response = await rezo.postJson('https://api.example.com/users', {
  name: 'Ada Lovelace',
  email: 'ada@example.com'
});

console.log(response.status); // 201
console.log(response.data);   // { id: 1, name: 'Ada Lovelace', ... }

The same pattern works for putJson and patchJson:

await rezo.putJson('https://api.example.com/users/1', { name: 'Ada L.' });
await rezo.patchJson('https://api.example.com/users/1', { email: 'new@example.com' });

POST with Form Data

URL-Encoded Form

const response = await rezo.postForm('https://api.example.com/login', {
  username: 'ada',
  password: 'secret'
});
// Content-Type: application/x-www-form-urlencoded

Multipart Form (File Uploads)

import { RezoFormData } from 'rezo';

const form = new RezoFormData();
form.append('name', 'profile-photo');
form.append('file', await Bun.file('./photo.jpg').arrayBuffer(), {
  filename: 'photo.jpg',
  contentType: 'image/jpeg'
});

const response = await rezo.postMultipart('https://api.example.com/upload', form);

You can also pass a plain object — Rezo converts it to multipart automatically:

const response = await rezo.postMultipart('https://api.example.com/upload', {
  name: 'document',
  metadata: JSON.stringify({ tags: ['report', '2024'] })
});

Creating Instances with Defaults

Create a dedicated client with shared configuration. Every request inherits these defaults:

import { Rezo } from 'rezo';

const api = new Rezo({
  baseURL: 'https://api.example.com/v2',
  timeout: 10_000,
  headers: {
    Authorization: 'Bearer sk-your-token',
    'Accept': 'application/json'
  },
  followRedirects: true,
  maxRedirects: 5
});

// Requests go to https://api.example.com/v2/users
const users = await api.get('/users');
const user = await api.get('/users/42');

Using rezo.create()

The default instance also has a create method that returns a new Rezo instance:

import rezo from 'rezo';

const github = rezo.create({
  baseURL: 'https://api.github.com',
  headers: { Accept: 'application/vnd.github.v3+json' }
});

const { data } = await github.get('/repos/nodejs/node');

Error Handling

All request errors throw a RezoError with structured information about what went wrong:

import rezo, { RezoError } from 'rezo';

try {
  await rezo.get('https://api.example.com/protected');
} catch (error) {
  if (error instanceof RezoError) {
    console.log(error.message);    // Human-readable error message
    console.log(error.code);       // Error code string, e.g. 'ERR_BAD_REQUEST'
    console.log(error.status);     // HTTP status code (if available)
    console.log(error.config);     // The request configuration
    console.log(error.response);   // The response object (if the server responded)
    console.log(error.suggestion); // Recovery suggestion

    // Boolean flags for common error categories
    if (error.isTimeout) {
      console.log('Request timed out');
    }
    if (error.isNetworkError) {
      console.log('Network connectivity issue');
    }
    if (error.isHttpError) {
      console.log(`Server returned ${error.status}`);
    }
    if (error.isRetryable) {
      console.log('This error is safe to retry');
    }
  }
}

Static Type Guard

Use RezoError.isRezoError() when you cannot use instanceof (for example, across module boundaries):

import { RezoError } from 'rezo';

try {
  await rezo.get('/endpoint');
} catch (error) {
  if (RezoError.isRezoError(error)) {
    // error is narrowed to RezoError
    console.log(error.code);
  }
}

Interceptors

Register request and response interceptors to transform data or handle cross-cutting concerns:

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

// Request interceptor -- runs before every request
client.interceptors.request.use(
  (config) => {
    config.headers = config.headers || {};
    config.headers['X-Request-Id'] = crypto.randomUUID();
    return config;
  },
  (error) => Promise.reject(error)
);

// Response interceptor -- runs after every response
client.interceptors.response.use(
  (response) => {
    console.log(`${response.config.method} ${response.finalUrl} -> ${response.status}`);
    return response;
  },
  (error) => {
    if (error.status === 401) {
      // Handle token refresh, redirect to login, etc.
    }
    return Promise.reject(error);
  }
);

Rezo manages cookies automatically. Every instance has a built-in cookie jar that stores cookies per domain and path, respects expiration, and sends the right cookies with each request.

import { Rezo, CookieJar } from 'rezo';

// Cookies persist in memory across requests
const client = new Rezo();
await client.get('https://example.com/login'); // receives Set-Cookie
await client.get('https://example.com/dashboard'); // sends cookie automatically

// Persist cookies to a file (JSON format)
const persistent = new Rezo({
  cookieFile: './cookies.json'
});
// Cookies are loaded from the file on construction
// and saved after each request automatically

// Or use Netscape format for curl/wget compatibility
const netscape = new Rezo({
  cookieFile: './cookies.txt'
});

// Share a cookie jar between multiple instances
const jar = new CookieJar();
const client1 = new Rezo({ jar });
const client2 = new Rezo({ jar });
// Both clients see each other's cookies

Streaming

The stream method returns a RezoStreamResponse that emits events as data arrives:

const stream = rezo.stream('https://api.example.com/events');

stream.on('start', (info) => {
  console.log('Request started:', info);
});

stream.on('headers', (info) => {
  console.log('Status:', info.status);
  console.log('Headers:', info.headers);
});

stream.on('data', (chunk) => {
  process.stdout.write(chunk);
});

stream.on('progress', (progress) => {
  console.log(`Downloaded: ${progress.loaded} bytes`);
});

stream.on('finish', (info) => {
  console.log('Stream complete:', info);
});

stream.on('error', (err) => {
  console.error('Stream error:', err.message);
});

Downloads

Download files to disk with real-time progress tracking:

const download = rezo.download(
  'https://releases.example.com/app-v2.zip',
  './downloads/app-v2.zip'
);

download.on('progress', (progress) => {
  const pct = ((progress.loaded / progress.total) * 100).toFixed(1);
  console.log(`${pct}% (${progress.loaded} / ${progress.total} bytes)`);
});

download.on('finish', (info) => {
  console.log('Download complete:', info.path);
});

download.on('error', (err) => {
  console.error('Download failed:', err.message);
});

Uploads

Upload files with progress tracking:

import { RezoFormData } from 'rezo';
import { readFileSync } from 'node:fs';

const fileBuffer = readFileSync('./report.pdf');

const upload = rezo.upload('https://api.example.com/files', fileBuffer, {
  headers: { 'Content-Type': 'application/pdf' }
});

upload.on('progress', (progress) => {
  console.log(`Uploaded: ${progress.loaded} bytes`);
});

upload.on('finish', (info) => {
  console.log('Upload complete');
});

Request Queue

Control concurrency and rate limiting across requests:

import { Rezo } from 'rezo';

const client = new Rezo({
  baseURL: 'https://api.example.com',
  queue: {
    concurrency: 5,        // max 5 requests in flight
    interval: 1000,        // per-second window
    intervalCap: 10        // max 10 requests per second
  }
});

// All requests go through the queue
const promises = Array.from({ length: 100 }, (_, i) =>
  client.get(`/items/${i}`)
);
const results = await Promise.all(promises);

What’s Next

  • Adapters — Learn about HTTP, HTTP/2, Fetch, XHR, cURL, and React Native adapters
  • Stealth Mode — Browser fingerprint emulation for web scraping
  • Crawler — Built-in site crawler with depth control and session management