Core Concepts

Making Requests

Rezo provides named methods for every standard HTTP method. Methods without a request body take (url, options?). Methods with a body take (url, data, options?). Each body method also has typed variants for JSON, form-encoded, and multipart data.

Importing

import rezo from 'rezo';

The default export is a pre-configured instance with the HTTP adapter. You can also create custom instances (see Instances).

Methods Without a Body

These methods accept (url, options?) and never send a request body.

GET

// Simple GET
const response = await rezo.get('https://api.example.com/users');
console.log(response.data);

// GET with query parameters
const response = await rezo.get('https://api.example.com/users', {
  params: { page: 2, limit: 10, role: 'admin' }
});

// GET with typed response
const response = await rezo.get<User[]>('https://api.example.com/users');
// response.data is typed as User[]

// GET with custom headers and timeout
const response = await rezo.get('https://api.example.com/users', {
  headers: { Authorization: 'Bearer token123' },
  timeout: 5000
});

Returns only the response headers. The response body is empty.

const response = await rezo.head('https://api.example.com/users');
console.log(response.headers.get('content-length'));
console.log(response.headers.get('last-modified'));

OPTIONS

Used for CORS preflight checks or discovering supported methods.

const response = await rezo.options('https://api.example.com/users');
console.log(response.headers.get('allow'));
// "GET, POST, PUT, DELETE, OPTIONS"

TRACE

Echoes back the received request. Primarily used for debugging.

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

DELETE

Sends a request with no body. For APIs that expect a body on DELETE, use rezo.request().

const response = await rezo.delete('https://api.example.com/users/42');
console.log(response.status); // 204

// DELETE with query parameters
const response = await rezo.delete('https://api.example.com/cache', {
  params: { key: 'user:42' }
});

Methods With a Body

These methods accept (url, data, options?) and send the data as the request body.

POST

The basic post method sends data as-is. Rezo auto-detects the content type from the data provided.

// POST with auto-detected content type
const response = await rezo.post('https://api.example.com/users', {
  name: 'Alice',
  email: 'alice@example.com'
});

PUT

Replaces a resource entirely.

const response = await rezo.put('https://api.example.com/users/42', {
  name: 'Alice Updated',
  email: 'alice@example.com',
  role: 'admin'
});

PATCH

Partially updates a resource.

const response = await rezo.patch('https://api.example.com/users/42', {
  role: 'admin'
});

Typed Body Variants

Every body method (post, put, patch) has three typed variants that explicitly set the Content-Type header and handle serialization.

postJson / putJson / patchJson

Serializes the data as JSON and sets Content-Type: application/json.

// POST JSON
const response = await rezo.postJson('https://api.example.com/users', {
  name: 'Alice',
  email: 'alice@example.com'
});

// PUT JSON with typed response
const response = await rezo.putJson<User>('https://api.example.com/users/42', {
  name: 'Alice',
  role: 'admin'
});

// PATCH JSON
const response = await rezo.patchJson('https://api.example.com/users/42', {
  role: 'admin'
});

// JSON arrays are also supported
await rezo.postJson('https://api.example.com/batch', [
  { action: 'create', name: 'Alice' },
  { action: 'create', name: 'Bob' }
]);

// Strings that are already JSON
await rezo.postJson('https://api.example.com/data', '{"key":"value"}');

postForm / putForm / patchForm

Serializes the data as URL-encoded form data and sets Content-Type: application/x-www-form-urlencoded.

// POST form data
const response = await rezo.postForm('https://api.example.com/login', {
  username: 'alice',
  password: 'secret123'
});

// PUT form data
await rezo.putForm('https://api.example.com/settings', {
  theme: 'dark',
  language: 'en'
});

// Also accepts URLSearchParams
const params = new URLSearchParams();
params.set('grant_type', 'authorization_code');
params.set('code', 'abc123');
await rezo.postForm('https://auth.example.com/token', params);

// Or a pre-encoded string
await rezo.postForm('https://api.example.com/data', 'key=value&foo=bar');

postMultipart / putMultipart / patchMultipart

Sends data as multipart/form-data, handling boundary generation automatically. Ideal for file uploads.

import { RezoFormData } from 'rezo';

// From a plain object
await rezo.postMultipart('https://api.example.com/upload', {
  name: 'profile-photo',
  file: someBuffer
});

// From RezoFormData
const form = new RezoFormData();
form.append('title', 'My Document');
form.append('file', buffer, { filename: 'doc.pdf', contentType: 'application/pdf' });
await rezo.postMultipart('https://api.example.com/documents', form);

// From native FormData (browser or Node.js 18+)
const nativeForm = new FormData();
nativeForm.append('name', 'test');
nativeForm.append('file', blob);
await rezo.postMultipart('https://api.example.com/upload', nativeForm);

// PUT multipart
await rezo.putMultipart('https://api.example.com/avatar', {
  image: avatarBuffer
});

The Generic request() Method

For full control over the request, use request(). It accepts a single configuration object.

const response = await rezo.request({
  url: 'https://api.example.com/users',
  method: 'POST',
  headers: { 'X-Custom-Header': 'value' },
  body: JSON.stringify({ name: 'Alice' }),
  timeout: 10000,
  responseType: 'json'
});

// DELETE with a body (not supported by rezo.delete directly)
const response = await rezo.request({
  url: 'https://api.example.com/batch',
  method: 'DELETE',
  body: JSON.stringify({ ids: [1, 2, 3] }),
  headers: { 'Content-Type': 'application/json' }
});

Callable Instance

The default rezo export is also callable as a function, following the Fetch API pattern.

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

// POST with Fetch-style options
const response = await rezo('https://api.example.com/users', {
  method: 'POST',
  body: JSON.stringify({ name: 'Alice' }),
  headers: { 'Content-Type': 'application/json' }
});

// Using Rezo's json shorthand in callable form
const response = await rezo('https://api.example.com/users', {
  method: 'POST',
  json: { name: 'Alice' }
});

// Using form shorthand
const response = await rezo('https://api.example.com/login', {
  method: 'POST',
  form: { username: 'alice', password: 'secret' }
});

Streaming, Downloads, and Uploads

These methods return event emitters instead of awaiting a full response.

stream()

Returns a RezoStreamResponse that emits data chunks as they arrive.

const stream = rezo.stream('https://example.com/large-file');

stream.on('data', (chunk) => {
  console.log('Received:', chunk.length, 'bytes');
});

stream.on('headers', (info) => {
  console.log('Status:', info.status);
  console.log('Content-Type:', info.headers.get('content-type'));
});

stream.on('progress', (p) => {
  console.log(`${p.percent}% complete`);
});

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

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

download()

Downloads a resource directly to a file. Returns a RezoDownloadResponse with progress events.

const download = rezo.download(
  'https://example.com/file.zip',
  './downloads/file.zip'
);

download.on('progress', (p) => {
  console.log(`${p.percent}% downloaded (${p.transferred} / ${p.total} bytes)`);
});

download.on('finish', (info) => {
  console.log('Saved to:', info.filePath);
  console.log('Total size:', info.totalBytes);
});

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

upload()

Uploads data with progress tracking. Returns a RezoUploadResponse.

import { readFileSync } from 'node:fs';

const fileBuffer = readFileSync('./large-file.bin');

const upload = rezo.upload('https://api.example.com/upload', fileBuffer);

upload.on('progress', (p) => {
  console.log(`${p.percent}% uploaded`);
});

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

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

Pagination

Rezo provides a built-in async iterator for paginated APIs.

// Auto-detect pagination from Link headers
for await (const page of rezo.paginate('https://api.example.com/users')) {
  console.log(page); // response.data for each page
}

// Cursor-based pagination
for await (const page of rezo.paginate('https://api.example.com/items', {
  pagination: {
    getNextUrl: (resp) => resp.data.next_cursor
      ? `/items?cursor=${resp.data.next_cursor}`
      : null
  }
})) {
  console.log(page);
}

// Collect all items with a transform
const allUsers = [];
for await (const items of rezo.paginate('/users', {
  pagination: {
    transform: (resp) => resp.data.results,
    requestLimit: 10 // Stop after 10 pages
  }
})) {
  allUsers.push(...items);
}