Skip to content

Commit 3bcf15d

Browse files
committed
feat: Implement cursor-based pagination in DataTable component and update related documentation
1 parent 2159edb commit 3bcf15d

File tree

15 files changed

+518
-35
lines changed

15 files changed

+518
-35
lines changed
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
---
2+
title: Pagination
3+
description: How to implement cursor-based pagination in VitNode applications.
4+
---
5+
6+
VitNode provides a powerful cursor-based pagination system that works seamlessly with the DataTable component. This documentation covers both backend implementation and frontend consumption of paginated data.
7+
8+
## Backend Implementation
9+
10+
VitNode uses cursor-based pagination for optimal performance with large datasets. The pagination system is implemented through the `withPagination` helper function.
11+
12+
### Basic Usage
13+
14+
```ts
15+
import { buildRoute } from '@/api/lib/route';
16+
import { dbClient } from '@/database/client';
17+
import { users } from '@/database/schema/users';
18+
import { z } from 'zod';
19+
20+
import {
21+
withPagination,
22+
zodPaginationPageInfo,
23+
zodPaginationQuery,
24+
} from '@/api/lib/with-pagination';
25+
26+
export const routeUsersGet = buildRoute({
27+
route: {
28+
isAuth: true,
29+
path: '/users',
30+
method: 'get',
31+
description: 'Get users list',
32+
request: {
33+
query: zodPaginationQuery.extend({
34+
order: z.enum(['asc', 'desc']).optional(),
35+
orderBy: z.enum(['id', 'username', 'createdAt']).optional(),
36+
}),
37+
},
38+
responses: {
39+
200: {
40+
content: {
41+
'application/json': {
42+
schema: z.object({
43+
edges: z.array(
44+
z.object({
45+
id: z.number(),
46+
username: z.string(),
47+
email: z.string(),
48+
createdAt: z.date(),
49+
}),
50+
),
51+
pageInfo: zodPaginationPageInfo,
52+
}),
53+
},
54+
},
55+
description: 'List of users',
56+
},
57+
},
58+
},
59+
handler: async c => {
60+
const query = c.req.valid('query');
61+
const data = await withPagination({
62+
params: {
63+
query,
64+
},
65+
primaryCursor: users.id, // Primary key used for pagination
66+
query: async ({ limit, where, orderBy }) =>
67+
await dbClient
68+
.select()
69+
.from(users)
70+
.where(where)
71+
.orderBy(orderBy)
72+
.limit(limit),
73+
table: users,
74+
orderBy: {
75+
column: query.orderBy ? users[query.orderBy] : users.createdAt,
76+
order: query.order ?? 'desc',
77+
},
78+
});
79+
80+
return c.json(data);
81+
},
82+
});
83+
```
84+
85+
### Pagination Parameters
86+
87+
The `withPagination` function accepts the following parameters:
88+
89+
| Parameter | Type | Description |
90+
| --------------- | -------- | -------------------------------------------- |
91+
| `params` | Object | Contains the query parameters for pagination |
92+
| `primaryCursor` | Column | The primary key column used for pagination |
93+
| `query` | Function | The database query function |
94+
| `table` | Table | The database table being queried |
95+
| `orderBy` | Object | The column and order to sort by |
96+
97+
### Zod Schemas
98+
99+
VitNode provides pre-defined Zod schemas for pagination:
100+
101+
- `zodPaginationQuery`: Schema for pagination query parameters
102+
- `zodPaginationPageInfo`: Schema for pagination information in the response
103+
104+
```ts
105+
// Example of zodPaginationQuery
106+
const zodPaginationQuery = z.object({
107+
cursor: z.string().optional(),
108+
first: z.string().transform(Number).optional(),
109+
last: z.string().transform(Number).optional(),
110+
});
111+
112+
// Example of zodPaginationPageInfo
113+
const zodPaginationPageInfo = z.object({
114+
startCursor: z.string().nullable(),
115+
endCursor: z.string().nullable(),
116+
hasNextPage: z.boolean(),
117+
hasPreviousPage: z.boolean(),
118+
});
119+
```
120+
121+
## Frontend Implementation
122+
123+
On the frontend, the pagination system works seamlessly with the DataTable component.
124+
125+
### Query Parameters
126+
127+
When fetching data from the API, include the pagination parameters in your request:
128+
129+
```tsx
130+
const res = await fetcher(userModule, {
131+
path: '/users',
132+
method: 'get',
133+
module: 'user',
134+
args: {
135+
query: {
136+
cursor: searchParams.cursor,
137+
first: searchParams.first,
138+
last: searchParams.last,
139+
order: searchParams.order,
140+
orderBy: searchParams.orderBy,
141+
},
142+
},
143+
withPagination: true, // Important flag for pagination
144+
});
145+
```
146+
147+
### SearchParamsDataTable Interface
148+
149+
VitNode provides a `SearchParamsDataTable` interface to type the search parameters:
150+
151+
```tsx
152+
export interface SearchParamsDataTable {
153+
cursor?: string;
154+
first?: string;
155+
last?: string;
156+
order?: 'asc' | 'desc';
157+
orderBy?: keyof DataTableTMin;
158+
}
159+
```
160+
161+
### Complete Example
162+
163+
Here's a complete example showing how to implement pagination in a Next.js page:
164+
165+
```tsx
166+
import { middlewareModule } from '@/api/modules/middleware/middleware.module';
167+
import {
168+
DataTable,
169+
SearchParamsDataTable,
170+
} from 'vitnode/components/table/data-table';
171+
import { fetcher } from 'vitnode/lib/fetcher';
172+
173+
export const UsersAdminView = async ({
174+
searchParams,
175+
}: {
176+
searchParams: Promise<SearchParamsDataTable>;
177+
}) => {
178+
const query = await searchParams;
179+
const res = await fetcher(middlewareModule, {
180+
path: '/users',
181+
method: 'get',
182+
module: 'middleware',
183+
args: {
184+
query,
185+
},
186+
withPagination: true,
187+
});
188+
const data = await res.json();
189+
190+
return (
191+
<div className="container mx-auto p-4">
192+
<DataTable
193+
columns={[
194+
{ id: 'id', label: 'ID' },
195+
{ id: 'username', label: 'Username' },
196+
{ id: 'email', label: 'Email' },
197+
{ id: 'createdAt', label: 'Created at' },
198+
]}
199+
edges={data.edges}
200+
order={{
201+
columns: ['id', 'username', 'email', 'createdAt'],
202+
defaultOrder: {
203+
column: 'createdAt',
204+
order: 'desc',
205+
},
206+
}}
207+
pageInfo={data.pageInfo}
208+
/>
209+
</div>
210+
);
211+
};
212+
```
213+
214+
## Pagination Object Structure
215+
216+
The pagination object returned from the API has the following structure:
217+
218+
```json
219+
{
220+
"edges": [
221+
{
222+
"id": 1,
223+
"username": "user1",
224+
"email": "user1@example.com",
225+
"createdAt": "2023-01-01T00:00:00.000Z"
226+
}
227+
// More items...
228+
],
229+
"pageInfo": {
230+
"startCursor": "MQ==", // Base64 encoded cursor
231+
"endCursor": "MTA=", // Base64 encoded cursor
232+
"hasNextPage": true,
233+
"hasPreviousPage": false
234+
}
235+
}
236+
```
237+
238+
## Advanced Usage
239+
240+
### Custom Filtering
241+
242+
You can extend the pagination query to include custom filtering:
243+
244+
```ts
245+
const query = c.req.valid('query');
246+
const data = await withPagination({
247+
params: {
248+
query,
249+
additionalWhere: eq(users.isActive, true), // Only active users
250+
},
251+
primaryCursor: users.id,
252+
query: async ({ limit, where, orderBy }) =>
253+
await dbClient
254+
.select()
255+
.from(users)
256+
.where(and(where, eq(users.isActive, true)))
257+
.orderBy(orderBy)
258+
.limit(limit),
259+
table: users,
260+
orderBy: {
261+
column: query.orderBy ? users[query.orderBy] : users.createdAt,
262+
order: query.order ?? 'desc',
263+
},
264+
});
265+
```
266+
267+
### Pagination Controls
268+
269+
The DataTable component automatically handles pagination controls when provided with the correct `pageInfo` object, allowing users to navigate through data with next/previous buttons and showing the current page information.

0 commit comments

Comments
 (0)