https://github.com/treedomtrees/paginated-connection
Paginated connection utility library
https://github.com/treedomtrees/paginated-connection
graphql nodejs pagination
Last synced: 10 months ago
JSON representation
Paginated connection utility library
- Host: GitHub
- URL: https://github.com/treedomtrees/paginated-connection
- Owner: treedomtrees
- License: mit
- Created: 2024-02-21T15:19:34.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-09-02T07:51:15.000Z (almost 2 years ago)
- Last Synced: 2025-08-08T23:27:33.631Z (10 months ago)
- Topics: graphql, nodejs, pagination
- Language: TypeScript
- Homepage:
- Size: 111 KB
- Stars: 1
- Watchers: 5
- Forks: 4
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# Paginated Connection
Paginated Connection is a utility library for handling pagination in your applications. It simplifies the process of managing paginated data, making it easy to integrate into your projects. It has built for GraphQL, and it's fully compliant with [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm)
__Made with β€οΈ atΒ Β [
](#-join-us-in-making-a-difference-)__, [join us in making a difference](#-join-us-in-making-a-difference-)!
## Table of Contents
- [Introduction](#introduction)
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Basic Example](#basic-example)
- [MySQL Example](#mysql-example)
- [MongoDB Example](#mongodb-example)
- [Return value](#return-value)
- [Edges](#edges)
- [Compose using getEdge](#compose-using-getEdge)
- [Compose using getEdges](#compose-using-getEdges)
- [Compose manually](#compose-manually)
- [Cursor Types](#cursor-types)
- [Default Cursor Type](#default-cursor-type)
- [Custom Cursor Type](#custom-cursor-type)
- [API Reference](#api-reference)
- [paginatedConnection](#paginatedconnection)
- [mysqlPaginatedConnection](#mysqlpaginatedconnection)
- [mongoDbPaginatedConnection](#mongodbpaginatedconnection)
- [Contributing](#contributing)
- [License](#license)
## Introduction
Pagination is essential for managing large datasets in a user-friendly manner. Paginated Connection provides a straightforward way to implement pagination logic in your applications, supporting both simple and complex use cases.
## Features
- Easy to set up and use
- Highly customizable
- Fully complaint with [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm)
- Supports various pagination strategies
- Works seamlessly with different data sources
## Installation
To install Paginated Connection:
```sh
npm install @treedom/paginated-connection
```
## Usage
### Basic example
Here is a basic example to get you started with Paginated Connection:
```typescript
import { paginatedConnection, PaginationInput } from '@treedom/paginated-connection'
// Define a simple node type
type Node = {
id: string;
};
// Define encode and decode functions
// Function to get cursor object from node
const getCursor = node => ({ after: node.id });
// encodeCursor should return a string
const encodeCursor = ({ node, getCursor }) => Buffer.from(JSON.stringify(getCursor())).toString('base64');
// decodeCursor should return an object
const decodeCursor = cursor => JSON.parse(Buffer.from(cursor, 'base64url').toString())
// Sample data loader
const dataLoader = async ({ cursor, first, encodeCursor, getEdge }) => {
// Fetch data based on cursor and first
const edges = fetchDataFromDataSource(cursor, first);
return {
edges: edges.map(node => getEdge(node, getCursor)),
hasNextPage: checkIfHasNextPage(),
};
};
// Sample count loader
const countLoader = async ({ cursor }) => {
return countDataFromDataSource(cursor);
};
const paginationInput: PaginationInput = { after: 'cursor123', first: 10 };
const paginationSafeLimit = 50;
const result = await paginatedConnection({
pagination: paginationInput,
paginationSafeLimit,
dataLoader,
encodeCursor,
decodeCursor,
countLoader,
});
console.log(result);
```
### Mysql Example
Using Paginated Connection with MySQL:
```typescript
import { mysqlPaginatedConnection } from '@treedom/paginated-connection';
// Define a simple node type
type Node = {
id: string;
};
// Function to get cursor object from node
const getCursor = node => ({ after: node.id });
// Define MySQL specific data loader
const mysqlDataLoader = async ({ cursor, first, encodeCursor }) => {
// Fetch data from MySQL database
const edges = fetchDataFromMySQL(cursor, first);
return {
edges: edges.map(node => getEdge(node, getCursor)),
};
};
// Define MySQL specific count loader
const mysqlCountLoader = async ({ cursor }) => {
return countDataInMySQL(cursor);
};
const paginationInput = { after: 'cursor123', first: 10 };
const paginationSafeLimit = 50;
const result = await mysqlPaginatedConnection({
pagination: paginationInput,
paginationSafeLimit,
dataLoader: mysqlDataLoader,
countLoader: mysqlCountLoader,
});
console.log(result);
```
In the MySQL implementation, the `+1` handling of data for the calculation of the `hasNextPage` value is implicitly managed by the function execution. This means you don't need to handle it yourself. The `hasNextPage` value is automatically calculated, so you should not return it in your data loader.
### MongoDB Example
Using Paginated Connection with MongoDB:
```typescript
import { mongoDbPaginatedConnection } from '@treedom/paginated-connection';
// Define a simple node type
type Node = {
id: string;
};
// Function to get cursor object from node
const getCursor = node => ({ after: node.id });
// Define MongoDB specific data loader
const mongoDbDataLoader = async ({ cursor, first, encodeCursor }) => {
// Fetch data from MongoDB
const edges = fetchDataFromMongoDB(cursor, first);
return {
edges: edges.map(node => getEdge(node, getCursor))
};
};
// Define MongoDB specific count loader
const mongoDbCountLoader = async ({ cursor }) => {
return countDataInMongoDB(cursor);
};
const paginationInput = { after: 'cursor123', first: 10 };
const paginationSafeLimit = 50;
const result = await mongoDbPaginatedConnection({
pagination: paginationInput,
paginationSafeLimit,
dataLoader: mongoDbDataLoader,
countLoader: mongoDbCountLoader,
});
console.log(result);
```
In the MongoDB implementation, the `+1` handling of data for the calculation of the `hasNextPage` value is implicitly managed by the function execution. This means you don't need to handle it yourself. The `hasNextPage` value is automatically calculated, so you should not return it in your data loader.
### Return value
Every paginatedConnection function returns an object of `PaginatedConnectionReturnType`:
```typescript
export type PaginatedConnectionReturnType = Promise<{
totalCount: () => Promise
pageInfo: {
endCursor: string
hasNextPage: boolean
}
edges: Array<{
node: TNode
cursor: string
}>
}>
```
where `TNode` is the type of the `node` loaded by `dataLoader` function.
## Edges
### Compose using getEdge
When executing `dataloader` function, it provides `getEdge` function, which is a shortcut to return an `Edge` object. Object returned by `getEdge` will contain both `node` and `cursor` values.
This function is very useful to avoid write boilerplate code to compose the `Edge` object, specially for cursor. Under the hood, it executes the `encodeCursor` function, providing cursor inside of return object `Edge`.
```typescript
const dataLoader = async ({ cursor, first, encodeCursor }) => {
const nodes = fetchDataFromDataSource(cursor, first);
return {
edges: nodes.map(node => getEdge(node, getCursor)),
hasNextPage: checkIfHasNextPage(),
};
};
```
Function `getEdge` gets in input:
- `node` object, which should has `TNode` type;
- `getCursor` function, which should returns an object of type `TCursor`.
### Compose using getEdges
When executing `dataloader` function, it provides `getEdges` function, which is a shortcut to return an `Edges` array. Every item returned by getEdges will contain both `node` and `cursor` values.
This function is very useful when you have an array of loaded items, which every item is already typed as `TNode` and ready to be used as a node inside `Edge`.
```typescript
const dataLoader = async ({ cursor, first, encodeCursor }) => {
const nodes = fetchDataFromDataSource(cursor, first); // nodes is an array of TNode object
return {
edges: getEdges(nodes, getCursor),
hasNextPage: checkIfHasNextPage(),
};
};
```
Function `getEdges` gets in input:
- `nodes` array, which should has `Array` type;
- `getCursor` function, which should returns an object of type `TCursor`.
Under the hood, it executes the `encodeCursor` function, in order to provide the cursor inside of `Edge`.
### Compose manually
If you need more customization of data, `Edges` could be manually composed, returning an array of `Edge`.
```typescript
const dataLoader = async ({ cursor, first, encodeCursor }) => {
const edges = fetchDataFromDataSource(cursor, first);
return {
edges: edges.map(node => ({ node, cursor: encodeCursor({ node, getCursor }) })),
hasNextPage: checkIfHasNextPage(),
};
};
```
## Cursor Types
### Default Cursor Type
By default, the cursor type only includes an `after` field, which is a string. This is simple and suitable for basic pagination scenarios.
```typescript
{ after: string };
```
The default cursor is used when no specific cursor type is provided to `paginatedConnection` (or `mysqlPaginatedConnection`, `mongoDbPaginatedConnection`, ecc...):
```typescript
type Node = {
id: string;
};
const paginationInput = { after: 'cursor123', first: 10 };
// Return value should be an object containing `after` field only
const getCursor = (node): { after: string } => ({
after: node.id,
});
// Here we're not passing any custom cursor type to paginatedConnection, so it'll use the default type
const result = await paginatedConnection({
...
dataLoader,
...
});
```
### Custom Cursor Type
For more complex scenarios, you can customize the cursor type to include additional fields, such as sorting information. The value of all cursor fields must be one of the following:
- `string`
- `number`
- `boolean`
```typescript
type CustomCursor = { after: string; sortField: string; sortOrder: 'asc' | 'desc', ranking: number, includeMetadata: boolean };
```
When using a custom cursor type, you need to type the `paginatedConnection` (or `mysqlPaginatedConnection`, `mongoDbPaginatedConnection`, ecc...), providing cursor custom type:
```typescript
import { paginatedConnection } from '@treedom/paginated-connection';
type Node = {
id: string;
sortField: string;
};
// Custom cursor type
type CustomCursor = { after: string; sortField: string; sortOrder: 'asc' | 'desc' };
// Return value should be an object of type `CustomCursor`
const getCursor = (node): CustomCursor => ({
after: node.id,
sortField: node.sortField,
sortOrder: 'asc',
});
// Sample data loader
const dataLoader = async ({ cursor, first, encodeCursor }) => {
const edges = fetchDataFromDataSource(cursor, first);
return {
edges: edges.map(node => getEdge(node, getCursor)),
hasNextPage: checkIfHasNextPage(),
};
};
// Provide CustomCursor type
const result = await paginatedConnection({
...
dataLoader,
...
});
console.log(result);
```
## API Reference
### paginatedConnection
`paginatedConnection(props: PaginatedConnectionProps)`
Handles pagination to offset-style ordering, returning Connection-style GraphQL result.
- props (`PaginatedConnectionProps`):
- `pagination` (`PaginationInput`): Pagination parameters.
- `paginationSafeLimit` (`number`): Safe limit for pagination.
- `dataLoader` (`(props: DataloaderProps) => Promise<{ edges: { node: TNode; cursor: string }[]; -- hasNextPage: boolean }>`): Data loader function.
- `encodeCursor` (`EncodeCursor`): Function to encode cursor, it should return a `string.`
- `decodeCursor` (`(cursor: string) => TCursor`): Function to decode cursor.
- `countLoader` (`(props: CountLoaderProps) => Promise`): Count loader function.
### mysqlPaginatedConnection
`mysqlPaginatedConnection(props: MysqlPaginatedConnectionProps)`
Handles pagination for MySQL databases, extending the basic `paginatedConnection`.
- props (`MysqlPaginatedConnectionProps`):
- `dataLoader` (`(props: DataloaderProps) => Promise<{ edges: { node: TNode; cursor: string }[]; }>`): MySQL data loader.
- `countLoader` (`(props: CountLoaderProps) => Promise`): MySQL count loader.
- `pagination` (`PaginationInput`): Pagination parameters.
- `paginationSafeLimit` (`number`): Safe limit for pagination.
In the MySQL implementation, the `+1` handling of data for the calculation of the `hasNextPage` value is implicitly managed by the function execution. This means you don't need to handle it yourself. The `hasNextPage` value is automatically calculated, so you should not return this value in your data loader.
### mongoDbPaginatedConnection
`mongoDbPaginatedConnection(props: MongoDbPaginatedConnectionProps)`
Handles pagination for MongoDB databases, extending the basic paginatedConnection.
- props (`MongoDbPaginatedConnectionProps`):
- `dataLoader` (`(props: DataloaderProps) => Promise<{ edges: { node: TNode; cursor: string }[]; }>`): MongoDB data loader.
- `countLoader` (`(props: CountLoaderProps) => Promise`): MongoDB count loader.
- `pagination` (`PaginationInput`): Pagination parameters.
- `paginationSafeLimit` (`number`): Safe limit for pagination.
In the MongoDB implementation, the `+1` handling of data for the calculation of the `hasNextPage` value is implicitly managed by the function execution. This means you don't need to handle it yourself. The `hasNextPage` value is automatically calculated, so you should not return this value in your data loader.
## π³ Join Us in Making a Difference! π³
We invite all developers who use Treedom's open-source code to support our mission of sustainability by planting a tree with us. By contributing to reforestation efforts, you help create a healthier planet and give back to the environment. Visit our [Treedom Open Source Forest](https://www.treedom.net/en/organization/treedom/event/treedom-open-source) to plant your tree today and join our community of eco-conscious developers.
Additionally, you can integrate the Treedom GitHub badge into your repository to showcase the number of trees in your Treedom forest and encourage others to plant new ones. Check out our [integration guide](https://github.com/treedomtrees/.github/blob/main/TREEDOM_BADGE.md) to get started.
Together, we can make a lasting impact! ππ
## Contributing
Contributions are welcome! Please read the contributing guidelines before submitting a pull request.
## License
This project is licensed under the MIT License.