Pagination
rezo.paginate() is an async generator that fetches pages from a paginated API one at a time. It auto-detects pagination via standard Link headers, or you can provide custom logic for cursor-based, offset-based, or any other pagination scheme.
Quick Start
import { Rezo } from 'rezo';
const rezo = new Rezo({ baseURL: 'https://api.example.com' });
// Auto-detect pagination via Link headers
for await (const page of rezo.paginate('/users')) {
console.log('Page data:', page);
} How It Works
On each iteration, paginate() makes a GET request and yields the page data. It then determines the next page URL:
- If you provide a
getNextUrlfunction, it calls that with the response. - Otherwise, it parses the
Linkheader and follows therel="next"URL. - If neither produces a next URL, iteration stops.
Auto-Detection via Link Headers
Many APIs return pagination links in the Link header (GitHub, GitLab, Stripe, etc.):
Link: <https://api.example.com/users?page=2>; rel="next", <https://api.example.com/users?page=5>; rel="last" Rezo parses this automatically:
for await (const users of rezo.paginate('/users')) {
for (const user of users) {
console.log(user.name);
}
} No configuration needed — if the server sends a Link header with rel="next", Rezo follows it.
Custom getNextUrl
For APIs that use cursors, tokens, or non-standard pagination, provide a getNextUrl function:
Cursor-Based
for await (const page of rezo.paginate('/items', {
pagination: {
getNextUrl: (response) => {
const cursor = response.data.next_cursor;
return cursor ? `/items?cursor=${cursor}` : null;
}
}
})) {
console.log('Items:', page.results);
} Offset-Based
let offset = 0;
const limit = 50;
for await (const page of rezo.paginate(`/records?offset=${offset}&limit=${limit}`, {
pagination: {
getNextUrl: (response) => {
offset += limit;
if (response.data.length < limit) return null; // Last page
return `/records?offset=${offset}&limit=${limit}`;
}
}
})) {
processRecords(page);
} Token-Based
for await (const page of rezo.paginate('/events', {
pagination: {
getNextUrl: (response) => {
const token = response.data.pagination?.next_token;
return token ? `/events?page_token=${token}` : null;
}
}
})) {
for (const event of page.events) {
handleEvent(event);
}
} Using Response Headers
for await (const page of rezo.paginate('/data', {
pagination: {
getNextUrl: (response) => {
return response.headers.get('x-next-page') || null;
}
}
})) {
console.log(page);
} Return null or undefined from getNextUrl to stop pagination.
Transform
By default, paginate() yields response.data from each page. Use transform to reshape or extract the data:
Extract Nested Results
const allUsers = [];
for await (const users of rezo.paginate('/users', {
pagination: {
transform: (response) => response.data.results
}
})) {
allUsers.push(...users);
}
console.log('Total users:', allUsers.length); Include Response Metadata
for await (const page of rezo.paginate('/items', {
pagination: {
transform: (response) => ({
items: response.data.items,
total: response.data.total_count,
status: response.status,
rateLimit: response.headers.get('x-ratelimit-remaining')
})
}
})) {
console.log(`${page.items.length} items (${page.total} total, rate limit: ${page.rateLimit})`);
} Request Limits
Prevent runaway pagination with countLimit or requestLimit (they are aliases):
// Fetch at most 10 pages
for await (const page of rezo.paginate('/users', {
pagination: {
countLimit: 10
}
})) {
console.log(page);
}
// Same thing using requestLimit
for await (const page of rezo.paginate('/users', {
pagination: {
requestLimit: 10
}
})) {
console.log(page);
} Without a limit, pagination continues until no next URL is found.
Collecting All Pages
async function fetchAll(url, options) {
const allItems = [];
for await (const page of rezo.paginate(url, {
pagination: {
transform: (r) => r.data.results,
...options
}
})) {
allItems.push(...page);
}
return allItems;
}
const users = await fetchAll('/users');
const repos = await fetchAll('/repos', { countLimit: 5 }); With Request Options
Pass any standard request options alongside pagination:
for await (const page of rezo.paginate('/private/data', {
headers: {
'Authorization': 'Bearer token123',
'Accept': 'application/json'
},
timeout: 10_000,
pagination: {
getNextUrl: (r) => r.data.next || null,
countLimit: 20
}
})) {
process.stdout.write('.');
} Full Example: GitHub API
const github = new Rezo({
baseURL: 'https://api.github.com',
headers: {
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`,
'Accept': 'application/vnd.github+json'
}
});
// Fetch all repositories for a user (auto-detects Link header pagination)
const allRepos = [];
for await (const repos of github.paginate('/users/octocat/repos?per_page=100')) {
allRepos.push(...repos);
console.log(`Fetched ${allRepos.length} repos so far...`);
}
console.log('Total repos:', allRepos.length); Full Example: Cursor-Based API
const api = new Rezo({ baseURL: 'https://api.example.com/v2' });
const allOrders = [];
for await (const batch of api.paginate('/orders', {
pagination: {
getNextUrl: (response) => {
const { has_more, cursor } = response.data.pagination;
return has_more ? `/orders?cursor=${cursor}&limit=100` : null;
},
transform: (response) => response.data.orders,
requestLimit: 50 // Safety limit
}
})) {
allOrders.push(...batch);
console.log(`Collected ${allOrders.length} orders`);
}