Documentation Index
Fetch the complete documentation index at: https://docs.snackbase.dev/llms.txt
Use this file to discover all available pages before exploring further.
Pagination allows you to retrieve large result sets in manageable chunks, improving performance and user experience.
Overview
Use pagination to retrieve records in pages:
const page1 = await client.records
.query("posts")
.page(1, 20)
.get();
console.log(page1.items); // 20 records
console.log(page1.total); // Total count
console.log(page1.skip); // 0
console.log(page1.limit); // 20
Use page() for page-based pagination:
const results = await client.records
.query("posts")
.page(1, 20) // Page 1, 20 items per page
.get();
Page Parameters
- page number: 1-based index (first page is 1)
- per page: Number of items per page
// Page 1
const page1 = await client.records
.query("posts")
.page(1, 20)
.get();
// Page 2
const page2 = await client.records
.query("posts")
.page(2, 20)
.get();
// Page 3
const page3 = await client.records
.query("posts")
.page(3, 20)
.get();
Use limit() and skip() for manual offset pagination:
const results = await client.records
.query("posts")
.skip(0)
.limit(20)
.get();
- skip: Number of records to skip (offset)
- limit: Maximum number of records to return
// First 20 records
const page1 = await client.records
.query("posts")
.skip(0)
.limit(20)
.get();
// Next 20 records
const page2 = await client.records
.query("posts")
.skip(20)
.limit(20)
.get();
// Next 20 records
const page3 = await client.records
.query("posts")
.skip(40)
.limit(20)
.get();
The pagination response includes metadata:
interface RecordListResponse<T> {
items: T[]; // Array of records
total: number; // Total count of matching records
skip: number; // Current offset
limit: number; // Current page size
}
Calculating Total Pages
Calculate the total number of pages:
const results = await client.records
.query("posts")
.page(1, 20)
.get();
const totalPages = Math.ceil(results.total / results.limit);
console.log(`Page 1 of ${totalPages}`);
Building a Paginator
Create a reusable pagination helper:
async function getPaginatedResults(
collection: string,
page: number,
perPage: number = 20
) {
const results = await client.records
.query(collection)
.page(page, perPage)
.get();
const totalPages = Math.ceil(results.total / perPage);
return {
items: results.items,
total: results.total,
page,
perPage,
totalPages,
hasNext: page < totalPages,
hasPrev: page > 1,
};
}
// Usage
const page = await getPaginatedResults("posts", 2, 20);
console.log(`Page ${page.page} of ${page.totalPages}`);
console.log("Has next:", page.hasNext);
console.log("Has previous:", page.hasPrev);
Combining with Filtering and Sorting
Combine pagination with filtering and sorting:
const results = await client.records
.query("posts")
.filter("status", "=", "published")
.sort("createdAt", "desc")
.page(1, 20)
.get();
The total count reflects all matching records, not just the current page.
Implement infinite scroll pagination:
let offset = 0;
const limit = 20;
let hasMore = true;
while (hasMore) {
const results = await client.records
.query("posts")
.skip(offset)
.limit(limit)
.get();
// Process results
results.items.forEach(post => {
console.log(post.title);
});
// Check if there are more results
hasMore = results.items.length === limit;
offset += limit;
}
React Example
Create a paginated list component:
import { useState, useEffect } from "react";
import { SnackBaseClient } from "@snackbase/sdk";
const client = new SnackBaseClient({
baseUrl: "https://api.example.com",
});
function PaginatedList() {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
const [loading, setLoading] = useState(false);
const perPage = 20;
useEffect(() => {
async function loadPosts() {
setLoading(true);
const results = await client.records
.query("posts")
.page(page, perPage)
.get();
setPosts(results.items);
setTotalPages(Math.ceil(results.total / perPage));
setLoading(false);
}
loadPosts();
}, [page]);
return (
<div>
<h1>Posts (Page {page} of {totalPages})</h1>
{loading ? (
<p>Loading...</p>
) : (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)}
<button
disabled={page === 1}
onClick={() => setPage(p => p - 1)}
>
Previous
</button>
<button
disabled={page === totalPages}
onClick={() => setPage(p => p + 1)}
>
Next
</button>
</div>
);
}
For large or frequently-changing datasets, cursor-based pagination provides consistent results without skipping or duplicating records.
async function getPaginatedResults(cursor: string | null = null) {
const params: any = { limit: 20 };
if (cursor) {
params.cursor = cursor;
}
const results = await client.records.list("posts", params);
return {
items: results.items,
nextCursor: results.next_cursor, // Cursor for next page
prevCursor: results.prev_cursor, // Cursor for previous page
hasMore: results.has_more, // Whether there are more results
};
}
Use cursor_before to paginate backwards:
const previousPage = await client.records.list("posts", {
limit: 20,
cursor_before: currentCursor,
});
When to Use Cursor vs Offset
| Approach | Best For |
|---|
Offset (page()) | Small datasets, known total count, jump to specific page |
| Cursor | Large datasets, real-time data, infinite scroll, consistent ordering |
To get the total count with cursor pagination, pass include_count: true in your query parameters.
1. Use Appropriate Page Sizes
Choose page sizes based on your use case:
// Mobile - smaller pages
const mobilePage = await client.records
.query("posts")
.page(1, 10)
.get();
// Desktop - larger pages
const desktopPage = await client.records
.query("posts")
.page(1, 50)
.get();
Reduce the result set before paginating:
// Good - filter first
const results = await client.records
.query("posts")
.filter("status", "=", "published")
.page(1, 20)
.get();
// Avoid - fetch all then paginate in code
const allPosts = await client.records.list("posts");
const page = allPosts.slice(0, 20);
Store total count and page metadata:
const cache = new Map();
async function getPage(page: number) {
const cacheKey = `posts:page:${page}`;
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
const results = await client.records
.query("posts")
.page(page, 20)
.get();
cache.set(cacheKey, results);
return results;
}
Complete Example
import { SnackBaseClient } from "@snackbase/sdk";
const client = new SnackBaseClient({
baseUrl: "https://api.example.com",
});
async function browsePosts() {
const perPage = 20;
let currentPage = 1;
// Get first page
const firstPage = await client.records
.query("posts")
.filter("status", "=", "published")
.sort("createdAt", "desc")
.page(currentPage, perPage)
.get();
const totalPages = Math.ceil(firstPage.total / perPage);
console.log(`Showing ${firstPage.items.length} of ${firstPage.total} posts`);
console.log(`Page ${currentPage} of ${totalPages}`);
// Get next page
if (currentPage < totalPages) {
const nextPage = await client.records
.query("posts")
.filter("status", "=", "published")
.sort("createdAt", "desc")
.page(currentPage + 1, perPage)
.get();
console.log("Next page:", nextPage.items);
}
}
Reference
page(pageNum, perPage?)
Set page number and size.
Parameters:
pageNum (number) - Page number (1-based)
perPage (number) - Items per page (default: 30)
Returns: QueryBuilder<T>
limit(count)
Set maximum records (manual pagination).
Parameters:
count (number) - Maximum records to return
Returns: QueryBuilder<T>
skip(count)
Set records to skip (manual pagination).
Parameters:
count (number) - Number of records to skip
Returns: QueryBuilder<T>
Next Steps