Gathering detailed insights and metrics for relay-pagination-directive
Gathering detailed insights and metrics for relay-pagination-directive
Gathering detailed insights and metrics for relay-pagination-directive
Gathering detailed insights and metrics for relay-pagination-directive
graphql-directive-pagination
Manipulates SQL clauses to provide pagination ability to fields in GraphQL. Easy to integrate.
graphql-pagination-transform
Transform fields into relay connections using a @connection directive
@kazekyo/nau
Nau is a tool that makes the use of Apollo Client more productive for users using Relay GraphQL Server Specification compliant backends.
@kazekyo/nau-graphql-codegen-preset
A GraphQL Code Generator preset for Nau
npm install relay-pagination-directive
Typescript
Module System
Node Version
NPM Version
JavaScript (68.08%)
TypeScript (31.92%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
4 Stars
181 Commits
2 Forks
34 Watchers
4 Branches
22 Contributors
Updated on Jul 07, 2025
Latest Version
1.1.1
Package Id
relay-pagination-directive@1.1.1
Unpacked Size
48.34 kB
Size
13.54 kB
File Count
26
NPM Version
10.8.2
Node Version
20.19.2
Published on
Jun 19, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
This library provides a GQL directive to more easily implement pagination based on the Relay specification. The directive options allow you to decide how far you wish to go towards full specification compliant pagination as often full compliance can add overhead which may not be required in your application.
Cursor-based pagination, the style implemented by Relay specifically, involves wrapping a dataset in a connection
object which presents your dataset items as a collection of edges
. When making your query you provide a first
(page size) value and an optional after
value. first
sets the number of items to return and after
sets the start point for item selection. For example, given 100 records with sequential IDs, your initial query could include a first
value of 10 and the second query would include 10 as the after
value (the id
of the last item on the first page).
To support this pattern in GQL we need to
Connection
and Edge
) typefirst
, after
) to each GQL field to be paginatedConnection
/Edge
schemaThis library aims to reduce this overhead by providing a GQL directive that allows you to tag the types you wish to paginate. It will then generate the appropriate types, with added connection arguments, for you and wrap your existing resolvers to generate the Connection
and Edge
objects.
yarn add relay-pagination-directive
# or
npm i relay-pagination-directive
# or
pnpm add relay-pagination-directive
Annotate any types you wish to paginate with the @connection
directive. Apply the provided GQL directive transformer and all the required Connection
and Edge
types will be created for you.
1"use strict"; 2 3const Fastify = require("fastify"); 4const mercurius = require(".."); 5const { connectionDirective } = require('relay-pagination-directive') 6 7const app = Fastify(); 8 9const dogs = [ 10 { 11 id: 1, 12 name: "Max", 13 }, 14 { 15 id: 2, 16 name: "Charlie", 17 }, 18 { 19 id: 3, 20 name: "Buddy", 21 }, 22 { 23 id: 4, 24 name: "Max", 25 }, 26]; 27 28 29const { connectionDirectiveTypeDefs, connectionDirectiveTransformer } = 30 connectionDirective() 31 32const typeDefs = ` 33 type Dog { 34 id: ID! 35 name: String! 36 owner: Human 37 } 38 39 type Query { 40 dogs: Dog @connection 41 } 42`; 43 44const resolvers = { 45 Query: { 46 dogs: (_, { first, after }) => { 47 const results = dogs 48 .sort((a, b) => { 49 return a.id - b.id 50 }) 51 .filter((dog) => (after ? dog.id > after : true)) 52 .slice(0, first) 53 54 return results 55 }, 56 }, 57}; 58 59const schema = makeExecutableSchema({ 60 typeDefs: [connectionDirectiveTypeDefs, typeDefs], 61 resolvers 62}) 63 64const connectionSchema = connectionDirectiveTransformer(schema) 65 66app.register(mercurius, { 67 schema: connectionSchema, 68 graphiql: true, 69}); 70 71app.listen({ port: 3000 });
This results in the following GQL schema being generated.
1directive @connection ON OBJECT_DEFINITION 2 3type PageInfo { 4 startCursor: String 5 endCursor: String 6 hasNextPage: Boolean! 7 hasPreviousPage: Boolean! 8} 9 10type Dog { 11 id: ID! 12 name: String! 13 owner: Human 14} 15 16type DogEdge { 17 cursor: ID! 18 node: Dog! 19} 20 21type DogConnection { 22 edges: [DogEdge!]! 23 pageInfo: PageInfo! 24} 25 26type Query { 27 dogs(first: Int, after: ID): DogConnection! 28}
By default, any field marked with the @connection
directive will have its resolver wrapped. This wrapper will await the result of your original resolver and perform the following depending on the result type it receives.
edges
value and continue to create the connection and edges objects. Edges will be created depending on the paginationMode
selected.pageInfo
property or additional properties on the connection1const resolvers = { 2 Query: { 3 users: async (root, args) => { 4 return db.getUsers(args) 5 /* 6 returns { 7 edges: DB result mapped depending on pagination mode 8 pageInfo: { 9 startCursor: ... 10 endCursor: ... 11 hasNextPage: ... 12 hasPreviousPage: ... 13 } 14 } 15 */ 16 }, 17 films: async (root, args) => { 18 const films = await db.getFilms(args) 19 return { 20 edges: films, 21 pageInfo: { 22 hasNextPage: true, 23 } 24 } 25 /* 26 returns { 27 edges: DB result mapped depending on pagination mode 28 pageInfo: { 29 startCursor: ... 30 endCursor: ... 31 hasNextPage: true <-- overridden value 32 hasPreviousPage: ... 33 } 34 } 35 */ 36 } 37 } 38}
Often it is desirable to add additional properties to our connection and/or edge objects. This is possible using the typeOptionsMap
config object. This is a mapping of the base type name to the configuration detail. For example, if we wanted to add a totalCount
property.
1... 2 3const typeOptionsMap = { 4 Dog: { 5 connectionProps: { 6 totalCount: 'Int!' 7 }, 8 } 9} 10 11const { connectionDirectiveTypeDefs, connectionDirectiveTransformer } = 12 connectionDirective() 13 14const typeDefs = ` 15 type Dog { 16 id: ID! 17 name: String! 18 owner: Human 19 } 20 21 type Query { 22 dogs: Dog @connection 23 } 24` 25 26const resolvers = { 27 Query: { 28 dogs: async (root, { first, after}) => { 29 const res = await db.getDogs(first, after) 30 31 return { 32 edges: res, 33 totalCount: res.length 34 } 35 } 36 } 37} 38 39...
It's often desirable to have multiple edge types (relationships) to the same base type. As in the example below, both the Person
and Cinema
types have a collection of Film
but in each instance the context is different and we want different properties to be available on the Edge
type. Therefore we have to create different Edge
types to represent the relationships.
To assist with this, it is possible to override the prefix used for generated connection and edge types. Imagine this schema
1type Film { 2 id: ID! 3 name: String! 4 release: Int! 5} 6 7type Person { 8 id: ID! 9 name: String! 10 born: Int! 11 films: [Film!] 12} 13 14type Cinema { 15 id: ID! 16 name: String! 17 nowShowing: [Film!] 18} 19
We may want to add pagination to Person.films
and Cinema.nowShowing
but the edges representing them might be different e.g.
1 2type Film { 3 id: ID! 4 name: String! 5 release: Int! 6} 7 8type PersonFilmEdge { 9 node: Film! 10 cursor: ID! 11 roles: [String!]! 12 performance: Int! 13} 14 15type PersonFilmConnection { 16 edges: [PersonFilmEdge!]! 17 pageInfo: PageInfo 18} 19 20type Person { 21 id: ID! 22 name: String! 23 born: Int! 24 films: PersonFilmConnection 25} 26 27type CinemaFilmEdge { 28 node: Film! 29 cursor: ID! 30 showingTimes: JSON! 31 price: Int! 32} 33 34type CinemaFilmConnection { 35 edges: [CinemaFilmEdge!]! 36 pageInfo: PageInfo 37} 38 39type Cinema { 40 id: ID! 41 name: String! 42 nowShowing: CinemaFilmConnection 43}
To achieve this we can supply an override prefix to the GQL directive. We can define the additional edge properties using the same edgeProps
option except that they are nested under the name of the custom prefix
1... 2 3const typeOptionsMap = { 4 Film: { 5 edgeProps: { 6 PersonFilm: { 7 roles: '[String!]!', 8 performance: 'Int!', 9 }, 10 CinemaFilm: { 11 showingTimes: 'JSON!', 12 price: 'Int!', 13 } 14 } 15 } 16} 17 18const { connectionDirectiveTypeDefs, connectionDirectiveTransformer } = 19 connectionDirective(typeOptionsMap) 20 21const typeDefs = ` 22 type Film { 23 id: ID! 24 name: String! 25 release: Int! 26 } 27 28 type Person { 29 id: ID! 30 name: String! 31 born: Int! 32 films: Film @connection(prefix: "PersonFilm") 33 } 34 35 type Cinema { 36 id: ID! 37 name: String! 38 nowShowing: Film @connection(prefix: "CinemaFilm") 39 } 40` 41...
For each type marked with the @connection
directive, you're able to specify the paginationMode
. This allows you to decide how far into the Pagination spec you wish to go. It may be that simply having the pageInfo
object and a list of results is enough for you. For this, enable 'simple'
pagination mode, otherwise omit this property and the default 'edges'
style will be used which follows the pagination spec for edges/nodes. When in 'simple'
mode the Edge
type is not created, instead the edges
property is given the type of the base type decorated with @connection
directive. Full example
1 const results = [ 2 { id: 1, name: 'foo' }, 3 { id: 2, name: 'bar' } 4 ] 5 6 /* simple mode response */ 7 { 8 edges: results, 9 pageInfo: { 10 startCursor: 'RmlsbTox', // Cursors are still generated depending on the cursor config 11 endCursor: 'RmlsbToy', 12 ... 13 } 14 } 15 16 /* edges mode response (default) */ 17 { 18 edges: [ 19 { 20 cursor: 1, 21 node: { id: 1, name: 'foo' }, 22 }, 23 { 24 cursor: 2, 25 node: { id: 2, name: 'bar' }, 26 }, 27 ], 28 pageInfo: { 29 startCursor: 'RmlsbTox', // Cursors are still generated depending on the cursor config 30 endCursor: 'RmlsbToy', 31 ... 32 } 33 }
connectionDirective(typeOptionsMap, directiveName)
connectionDirective
returns an object providing the type definition and schema transformer required to use the connection directive.
typeOptionsMap
- object (optional) - Map of GQL types (types with the @connection
directive) and their configuration to support the pagination process
paginationMode
- string<'simple' | 'edges'> (optional) - when using simple pagination mode the results array will be added to connection.edges
without any adjustments i.e. the edge relationship will be removed and no cursor mapping will occur, cursors will still be generated for the startCursor
and endCursor
values.encodeCursor
- boolean - Should cursor values be encoded to base64, defaults to true. Note This value is ignored when a cursor function is provided.cursorPropOrFn
- string|function(optional) - By default, the id
property of an item will be used as its cursor value, however, it is recommended that cursors should be opaque string values. This option allows you to provide a different property name to use for the cursor value or a function that will be passed the base type and item and should return a string cursor value. encodeCursor
and decodeCursor
functions helpers are provided to assist with this. Full exampledirectiveName
- string (optional, defaults to 'connection') - Custom name to be used for the directiveAn object with the following properties
connectionDirectiveTypeDefs
- string - GQL type definition for the connection directive. This must be included in your typesconnectionDirectiveTransformer
- function - The directive transformer function to be used to wrap your schema and generate the pagination types. NOTE You must have a GraphQLSchema object to pass to the transformer function, so we recommend using makeExecutableSchema
. However, due to the reassignment of types in the directive your resolvers should still be passed in the Mercurius options.typeName
- string - Value of the GQL type represented by this itemid
- string|number - Unique identifier for this item, should be serialisable to a stringBase64 string to be used as a unique identifier
cursor
- string - Base64 string value to decodeAn object with the following properties
typeName
- string - Value of the GQL type represented by this itemid
- string - Unique identifier for this itemThe NodesOnly
utility type simplifies resolver typings by unwrapping connection
objects and exposing only the underlying node data.
In some cases, such as when using pagination patterns, the public schema may define connections,
while the actual resolver logic returns plain node data. NodesOnly
adjusts the expected resolver
typings to reflect this, unwrapping connection layers and exposing the underlying data.
This is particularly useful when resolver types are auto-generated from the schema, as with tools
like mercurius-codegen
or @graphql-tools/codegen.
1import type { NodesOnly } from "relay-pagination-directive"; 2import type { Resolvers } from "./generated"; 3 4// ... 5 6/** 7 * If `Resolvers` was automatically generated from the schema, it likely 8 * expects data wrapped in Relay-style connection objects. 9 * 10 * Using `NodesOnly<Resolvers>` allows you to return plain arrays of data, 11 * while the connection wrappers (edges, pageInfo, etc.) are added under the hood. 12 */ 13const resolvers: NodesOnly<Resolvers> = { 14 Query: { 15 dogs: (_, { first, after }) => { 16 const results = dogs 17 .sort((a, b) => a.id - b.id) 18 .filter(dog => (after ? dog.id > after : true)) 19 .slice(0, first); 20 21 return results; 22 }, 23 }, 24}; 25
hasNextPage
StrategiesThe intention of the pageInfo.hasNextPage
property is to indicate to the consuming application whether it needs to query for subsequent pages of results. By default, this library will set this value to true if the data set returned from your resolver has the same length as the first
argument. Depending on your use case this could be enough for you. For example, in a scenario where you're using pagination to load a large dataset and your UI does not display page numbers having another page is not a problem. However, if your pagination is enabling some Next
style UI button this would be bad as the user could click next and receive a blank page.
These are two additional strategies that can be used to generate your own hasNextPage
value and provide it in the resolver response.
first
value e.g. if 100 items are requested, query your DB for 101. You'll then know if 101 records are returned that there is at least 1 more page.You can view full examples of all three strategies in this example
No vulnerabilities found.
No security vulnerabilities found.