Paginated listing
When building dashboards, file managers, or catalog interfaces, you often need to list files or KV keys stored inside your Storage system. Because buckets and KV namespaces can contain millions of objects, retrieving everything in a single call is impossible.
Layeron Storage provides Cursor-Based Pagination via the .list() operation, allowing you to fetch objects page-by-page efficiently.
Code Implementation
Section titled “Code Implementation”This example demonstrates writing an API endpoint for an administrative file dashboard that retrieves up to 20 files under a specific prefix, support filtering, and returns a cursor for the next page.
import { backend } from "@layeron/core"import { storage } from "@layeron/modules"
const app = backend()
// Declare the media bucketconst mediaBucket = storage.bucket({ name: "project-media", access: "private",})
app.use(mediaBucket)
// Route: List Files with Cursor Paginationapp.get("/api/admin/files", async (request) => { const url = new URL(request.url)
// Extract pagination query parameters const prefix = url.searchParams.get("prefix") ?? "uploads/" // Filter by directory/folder prefix const limit = parseInt(url.searchParams.get("limit") ?? "20", 10) // Limit size (default: 20) const cursor = url.searchParams.get("cursor") ?? undefined; // Get the next-page cursor marker
// Query paginated objects from Storage const listResult = await mediaBucket.list({ prefix, limit: Math.min(limit, 100), // Enforce a safety ceiling of 100 items per page cursor, })
// Format list items const files = listResult.items.map((item) => ({ key: item.key, sizeBytes: item.size, contentType: item.contentType ?? "application/octet-stream", etag: item.etag, uploadedAt: item.storedAt, metadata: item.metadata, }))
// Return items and next-page cursor return Response.json({ files, pagination: { limit, // The cursor is a string token generated by Cloudflare. // If there are no more items left, cursor will be undefined. nextCursor: listResult.cursor, hasMore: !!listResult.truncated, // True if more items match the query }, })})Client-Side Infinite Scroll Integration (Frontend)
Section titled “Client-Side Infinite Scroll Integration (Frontend)”On your client-side frontend, fetch the first page, and supply the returned nextCursor on subsequent fetches to load more files:
let currentCursor = nulllet filesList = []
async function loadNextPage() { const url = new URL("/api/admin/files", window.location.origin) url.searchParams.set("prefix", "uploads/") url.searchParams.set("limit", "20") if (currentCursor) { url.searchParams.set("cursor", currentCursor) }
const response = await fetch(url) const data = await response.json()
// Append new files to the active list filesList = [...filesList, ...data.files] renderFiles(filesList)
// Update cursor pointer for the next scroll trigger currentCursor = data.pagination.nextCursor
// Hide the "Load More" button if hasMore is false if (!data.pagination.hasMore) { document.getElementById("load-more-btn").style.display = "none" }}How It Works
Section titled “How It Works”- Prefix Filtering: Cloudflare’s storage engine evaluates the
prefixquery index-optimally. In this example, querying"uploads/"retrieves only keys starting with that string, effectively simulating hierarchical folder paths. - Limit Boundaries: You can declare a custom
limitper page (up to100items). Smaller limits speed up edge response and serialization times. - Cursor Iteration: The returned
cursoris a secure, stateful string generated by Cloudflare. When supplied in a subsequent.list({ cursor })call, it acts as a high-performance database offset marker, continuing retrieval exactly where the previous page left off without needing to scan previous records.