Gathering detailed insights and metrics for mongoose-relay-paginate
Gathering detailed insights and metrics for mongoose-relay-paginate
Gathering detailed insights and metrics for mongoose-relay-paginate
Gathering detailed insights and metrics for mongoose-relay-paginate
Mongoose cursoring based pagination plugin with GraphQL relay compatibility
npm install mongoose-relay-paginate
Typescript
Module System
Node Version
NPM Version
TypeScript (87.44%)
JavaScript (8.79%)
CSS (2.93%)
MDX (0.49%)
Makefile (0.35%)
Total Downloads
11,812
Last Day
10
Last Week
34
Last Month
113
Last Year
4,240
MIT License
3 Stars
47 Commits
1 Forks
2 Watchers
2 Branches
1 Contributors
Updated on Aug 13, 2024
Minified
Minified + Gzipped
Latest Version
6.0.2
Package Id
mongoose-relay-paginate@6.0.2
Unpacked Size
72.38 kB
Size
15.83 kB
File Count
14
NPM Version
10.2.3
Node Version
20.10.0
Published on
Jan 24, 2024
Cumulative downloads
Total Downloads
Last Day
150%
10
Compared to previous day
Last Week
61.9%
34
Compared to previous week
Last Month
-69%
113
Compared to previous month
Last Year
15.1%
4,240
Compared to previous year
Your instance of MongoDB should be running version 4.4 or later. Your version of mongoose should be 7.5.1 or later
View the docs
The changelog is available here.
This library is meant to allow cursor based pagination between the client and the server. In many ways this cursor based way is similar to the way you would use skip and limit.
Cursor based pagination is a technique that is used by the likes of Facebook and Twitter on their Feeds.
Personally I recommend cursor based pagination if you are doing something like an infinite scroll feed. If however you are doing something more like a table of data where you want to select a page of results skip and limit will probably be a better option.
Technical side note: Although if you really need the selection of a page of results you could technically combine the approaches by providing a skip while still using the library's cursor based PagingInfo, and then you'd get the best of both. Although I would suggest using the library's PagingInfo where possible and only reach for skip if you absolutely need it.
Imagine you're building a web api and you want to allow your users to request a collection of data, but you do not want to give them all the data back at once. You would generally reach for two very typical things in mongodb skip and limit, though like everything there are pros and cons to this approach which were explained in the previous section. Basically to use skip and limit you would pass them from your client to your server in an API route. Your API route might accept an ISkipLimit like so:
1interface ISkipLimit { 2 skip: number; 3 limit: number; 4}
Then calling a function like below in your api get route or graphql query:
1function findUsers(skipLimit: ISkipLimit): Promise<User[]> { 2 return UserModel.find({ 3 //... Some possible conditions or none is fine too. 4 }).skip(skipLimit.skip).limit(skipLimit.limit).exec(); 5}
Notice the type returned here is your typical array of users.
Your front end would then have to somehow communicate the ISkipLimit interface to the backend, so it could do the above. You would typically do that with the use of your request's query parameters or the request's body.
The pagination process with a cursor is quite a bit more involved to implement from scratch that's where this library comes in, so that the process of using cursor based pagination will be simpler for mongodb users. With this library paginating with cursor based pagination we use the library's provided type PagingInfo
to communicate to the server the same logic a typical skip and limit would provide, but the type looks like this instead:
1/** Info about how to page forward and backward 2 * 3 * 4 * `first` and `last` are alot like limit in a typical skip and limit scheme. 5 * This is because first and last signify how many elements to return. 6 * You should never supply both `first` and `last` at the same time. 7 * You should either supply one or the other, but not both. 8 * Supplying both will lead to unpredicted behaviour. 9 * 10 * `after` and `before` are more like the typical skip in skip and limit. 11 * This is because after and before signify where the 12 * collection starts and stops searching. 13 * You may supply both the after and before, but your before cursor must be later 14 * in your collection then your after cursor otherwise you will get 0 results. 15 * 16 * @public 17 */ 18export type PagingInfo<DocType = unknown> = { 19 /** fetch the `first` given number of records */ 20 first?: number; 21 /** fetch the `last` given number of records */ 22 last?: number; 23 /** fetch `after` the given record's cursor */ 24 after?: PagingCursor<DocType> | null | undefined; 25 /** fetch `before` the given record's cursor */ 26 before?: PagingCursor<DocType> | null | undefined; 27};
You may be wondering what the
before
andafter
is and what exactly thePagingCursor
type is, but we'll get to that after we show you the return result of the plugin.
So this is now the data used between the client and server to communicate what part of the data we want to return from the query. The first
signifies to fetch the given first
number of records from the collection. The last
signifies to fetch the given last
number of records. The after
signifies starting from some record's cursor fetch after
that record. The before
signifies starting from some record's cursor fetch before
that record.
This pagingInfo is passed into the .relayPaginate()
method that is available on your queries once this library has properly been initialized. So, you would use that in a route like this:
1function findUsers(pagingInfo: PagingInfo<User>): Promise<RelayResult<User[]>> { 2 return UserModel.find({ 3 //... Some possible conditions or none is fine too. 4 }).relayPaginate(pagingInfo).exec(); 5}
But wait the notice the type returned here from the plugin is not just your typical User array (User[]
). Instead it is a RelayResult<User[]>
which the RelayResult looks like so:
1export interface RelayResult<Nodes extends unknown[]> { 2 edges: { 3 node: ElementOfArray<Nodes>; 4 cursor: PagingCursor<ElementOfArray<Nodes>>; 5 }[]; 6 nodes: Nodes; 7 pageInfo: { 8 hasNextPage: boolean; 9 hasPreviousPage: boolean; 10 endCursor?: PagingCursor<ElementOfArray<Nodes>> | null; 11 startCursor?: PagingCursor<ElementOfArray<Nodes>> | null; 12 }; 13}
So it looks like our User[]
is just our relayResult.nodes
, so our frontend can use the user array like that, but also there is some other metadata about the paging process being done which would allow our frontend to control the pagination for us (which is why we would send the whole RelayResult<User[]>
to the frontend and not just the nodes.). Also if you were being quite observant you may notice that mysterious PagingCursor
from our PagingInfo
's after
and before
properties. This is because those cursors can be passed back to our server through the PagingInfo
as our before
or after
cursor. This process of sending the data back into the relayPaginate plugin is outlined in more detail in the Paging page.
Because no existing pagination for mongoose that I can find was all of the following:
Q Doesn't MongoDB already have a built-in cursoring mechanism, why reinvent the wheel?
A Yes it does already have one, but that is meant to be used between the server and the database where as this library provides the cursoring/paging to be done between the client and the server.
To use this library first install in your project, like so:
1npm i mongoose-relay-paginate
Then you need to register the plugin sometime before you create your models see mongoose's global plugins documentation for help:
1import { plugin, Model, model, Schema } from "mongoose"; 2import { relayPaginatePlugin, RelayPaginateQueryHelper, RelayPaginateStatics } from "mongoose-relay-paginate"; 3 4// 0. Register the relay paginate plugins. 5plugin( 6 relayPaginatePlugin({ 7 // Send in options 8 maxLimit: 100, 9 }) 10); 11 12// 1. Create an interface representing a document in MongoDB. 13interface User { 14 _id: mongoose.Types.ObjectId; 15 myId: number; 16 name: string; 17 email: string; 18 avatar?: string; 19} 20 21// 2. Setup various types. 22type UserModel = Model<User, RelayPaginateQueryHelper> & RelayPaginateStatics; 23 24// 3. Create a Schema corresponding to the document interface. 25const schema = new Schema<User, UserModel>({ 26 myId: Number, 27 name: { type: String, required: true }, 28 email: { type: String, required: true }, 29 avatar: String, 30}); 31 32// 4. Create your Model. 33const UserModel = model<User, UserModel>("User", schema);
Now the relayPaginate should be available on your model's mongoose queries, so you can use it as shown below.
1 2const result = await UserModel.find() 3 // sorting by id from largest (most recent)--> to smallest (most early) using mongoose's default sort. 4 .sort({ _id: -1 }) 5 // This library's `relayPaginate` can now be used off your query 6 // after the above setup. 7 .relayPaginate({ 8 first: 1, 9 });
Or use an aggregate query off of your model:
1const result = await UserModel 2 // sorting by id from largest-->smallest using mongoose's default sort. 3 .aggregateRelayPaginate( 4 [{$sort: {_id: 1}}], 5 { 6 first: 1, 7 });
The result will always come back in the form of:
1type result = { 2 nodes: UserModel[], 3 edges: { 4 node: UserModel, 5 cursor: { 6 // whatever fields were sorted on or just _id 7 } 8 }, 9 pageInfo: { 10 hasNextPage: boolean; 11 hasPreviousPage: boolean; 12 endCursor: { 13 // whatever fields were sorted on or just _id 14 }; 15 startCursor: { 16 // whatever fields were sorted on or just _id 17 }; 18 } 19}
For more details view the docs.
No vulnerabilities found.
No security vulnerabilities found.