{"id":22325759,"url":"https://github.com/equalogic/nestjs-graphql-connection","last_synced_at":"2025-10-24T02:00:14.955Z","repository":{"id":37215871,"uuid":"245270921","full_name":"equalogic/nestjs-graphql-connection","owner":"equalogic","description":"Relay-style pagination for NestJS GraphQL server.","archived":false,"fork":false,"pushed_at":"2024-04-13T18:28:39.000Z","size":4173,"stargazers_count":14,"open_issues_count":10,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-04-14T00:37:09.797Z","etag":null,"topics":["connection","edge","graphql","graphql-relay","nestjs","node","relay"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/equalogic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2020-03-05T21:25:21.000Z","updated_at":"2024-04-15T06:21:05.386Z","dependencies_parsed_at":"2024-02-18T13:44:25.446Z","dependency_job_id":"0658f3c5-8806-4c91-8108-2bbd17ba78e0","html_url":"https://github.com/equalogic/nestjs-graphql-connection","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/equalogic%2Fnestjs-graphql-connection","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/equalogic%2Fnestjs-graphql-connection/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/equalogic%2Fnestjs-graphql-connection/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/equalogic%2Fnestjs-graphql-connection/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/equalogic","download_url":"https://codeload.github.com/equalogic/nestjs-graphql-connection/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228030027,"owners_count":17858432,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["connection","edge","graphql","graphql-relay","nestjs","node","relay"],"created_at":"2024-12-04T02:13:35.243Z","updated_at":"2025-10-24T02:00:09.929Z","avatar_url":"https://github.com/equalogic.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/equalogic/nestjs-graphql-connection/raw/master/resources/logo@720w.png\" width=\"480\" height=\"364\"\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://npmjs.com/package/nestjs-graphql-connection\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/nestjs-graphql-connection\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://npmjs.com/package/nestjs-graphql-connection\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dy/nestjs-graphql-connection\"\u003e\n  \u003c/a\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n\u003c/div\u003e\n\n[Relay-style pagination](https://relay.dev/graphql/connections.htm) for NestJS GraphQL server.\n\n## Installation\n\n```shell\nnpm i nestjs-graphql-connection\n```\n\nTypeScript type definitions are included in the box.\n\nYou must also install [@nestjs/graphql](https://github.com/nestjs/graphql) as a peer dependency (you should have this\nalready).\n\n## Usage\n\n### Create an Edge type\n\nExtend a class from `createEdgeType` function, passing it the class of objects to be represented by the edge's `node`.\n\n```ts\nimport { ObjectType } from '@nestjs/graphql';\nimport { createEdgeType } from 'nestjs-graphql-connection';\nimport { Person } from './entities';\n\n@ObjectType()\nexport class PersonEdge extends createEdgeType(Person) {}\n```\n\n### Create a Connection type\n\nExtend a class from `createConnectionType` function, passing it the class of objects to be represented by the\nconnection's `edges`:\n\n```ts\nimport { ObjectType } from '@nestjs/graphql';\nimport { createConnectionType } from 'nestjs-graphql-connection';\n\n@ObjectType()\nexport class PersonConnection extends createConnectionType(PersonEdge) {}\n```\n\n### Create a Connection Arguments type\n\nExtend a class from `ConnectionArgs` class to have pagination arguments pre-defined for you. You can additionally\ndefine your own arguments for filtering, etc.\n\n```ts\nimport { ArgsType, Field, ID } from '@nestjs/graphql';\nimport { ConnectionArgs } from 'nestjs-graphql-connection';\n\n@ArgsType()\nexport class PersonConnectionArgs extends ConnectionArgs {\n  /*\n   * PersonConnectionArgs will inherit `first`, `last`, `before`, `after`, and `page` fields from ConnectionArgs\n   */\n\n  // EXAMPLE: Defining a custom argument for filtering\n  @Field(type =\u003e ID, { nullable: true })\n  public personId?: string;\n}\n```\n\n### Create a Connection Builder\n\nNow define a `ConnectionBuilder` class for your `Connection` object. The builder is responsible for interpreting\npagination arguments for the connection, and creating the cursors and `Edge` objects that make up the connection.\n\n```ts\nimport {\n  ConnectionBuilder,\n  Cursor,\n  EdgeInputWithCursor,\n  PageInfo,\n  validateParamsUsingSchema,\n} from 'nestjs-graphql-connection';\n\nexport type PersonCursorParams = { id: string };\nexport type PersonCursor = Cursor\u003cPersonCursorParams\u003e;\n\nexport class PersonConnectionBuilder extends ConnectionBuilder\u003c\n  PersonConnection,\n  PersonConnectionArgs,\n  PersonEdge,\n  Person,\n  PersonCursor\n\u003e {\n  public createConnection(fields: { edges: PersonEdge[]; pageInfo: PageInfo }): PersonConnection {\n    return new PersonConnection(fields);\n  }\n\n  public createEdge(fields: EdgeInputWithCursor\u003cPersonEdge\u003e): PersonEdge {\n    return new PersonEdge(fields);\n  }\n\n  public createCursor(node: Person): PersonCursor {\n    return new Cursor({ id: node.id });\n  }\n\n  public decodeCursor(encodedString: string): PersonCursor {\n    // A cursor sent to or received from a client is represented as a base64-encoded, URL-style query string containing\n    // one or more key/value pairs describing the referenced node's position in the result set (its ID, a date, etc.)\n    // Validation is optional, but recommended to enforce that cursor values supplied by clients must be well-formed.\n    // This example uses Joi for validation, see documentation at https://joi.dev/api/?v=17#object\n    // The following schema accepts only an object matching the type { id: string }:\n    const schema: Joi.ObjectSchema\u003cPersonCursorParams\u003e = Joi.object({\n      id: Joi.string().empty('').required(),\n    }).unknown(false);\n\n    return Cursor.fromString(encodedString, params =\u003e validateParamsUsingSchema(params, schema));\n  }\n}\n```\n\n### Resolve a Connection\n\nYour resolvers can now return your `Connection` as an object type. Use your `ConnectionBuilder` class to determine which\npage of results to fetch and to create the `PageInfo`, cursors, and edges in the result.\n\n```ts\nimport { Args, Query, Resolver } from '@nestjs/graphql';\n\n@Resolver()\nexport class PersonQueryResolver {\n  @Query(returns =\u003e PersonConnection)\n  public async persons(@Args() connectionArgs: PersonConnectionArgs): Promise\u003cPersonConnection\u003e {\n    const { personId } = connectionArgs;\n\n    // Create builder instance\n    const connectionBuilder = new PersonConnectionBuilder(connectionArgs);\n\n    // EXAMPLE: Count the total number of matching persons (without pagination)\n    const totalEdges = await countPersons({ where: { personId } });\n\n    // EXAMPLE: Do whatever you need to do to fetch the current page of persons\n    const persons = await fetchPersons({\n      where: { personId },\n      take: connectionBuilder.edgesPerPage, // how many rows to fetch\n    });\n\n    // Return resolved PersonConnection with edges and pageInfo\n    return connectionBuilder.build({\n      totalEdges,\n      nodes: persons,\n    });\n  }\n}\n```\n\n## Using Offset Pagination\n\nWith offset pagination, cursor values are an encoded representation of the row offset. It is possible for clients to\npaginate by specifying either an `after` argument with the cursor of the last row on the previous page, or to pass a\n`page` argument with an explicit page number (based on the rows per page set by the `first` argument). Offset paginated\nconnections do not support the `last` or `before` connection arguments, results must be fetched in forward order.\n\nOffset pagination is useful when you want to be able to retrieve a page of edges at an arbitrary position in the result\nset, without knowing anything about the intermediate entries. For example, to link to \"page 10\" without first\ndetermining what the last result was on page 9.\n\nTo use offset cursors, extend your builder class from `OffsetPaginatedConnectionBuilder` instead of `ConnectionBuilder`:\n\n```ts\nimport {\n  EdgeInputWithCursor,\n  OffsetPaginatedConnectionBuilder,\n  PageInfo,\n  validateParamsUsingSchema,\n} from 'nestjs-graphql-connection';\n\nexport class PersonConnectionBuilder extends OffsetPaginatedConnectionBuilder\u003c\n  PersonConnection,\n  PersonConnectionArgs,\n  PersonEdge,\n  Person\n\u003e {\n  public createConnection(fields: { edges: PersonEdge[]; pageInfo: PageInfo }): PersonConnection {\n    return new PersonConnection(fields);\n  }\n\n  public createEdge(fields: EdgeInputWithCursor\u003cPersonEdge\u003e): PersonEdge {\n    return new PersonEdge(fields);\n  }\n\n  // When extending from OffsetPaginatedConnectionBuilder, cursor encoding/decoding always uses the OffsetCursor type.\n  // So it's not necessary to implement the createCursor() or decodeCursor() methods here.\n}\n```\n\nIn your resolver, you can use the `startOffset` property of the builder to determine the zero-indexed offset from which\nto begin the result set. For example, this works with SQL databases that accept a `SKIP` or `OFFSET` parameter in\nqueries.\n\n```ts\nimport { Args, Query, Resolver } from '@nestjs/graphql';\n\n@Resolver()\nexport class PersonQueryResolver {\n  @Query(returns =\u003e PersonConnection)\n  public async persons(@Args() connectionArgs: PersonConnectionArgs): Promise\u003cPersonConnection\u003e {\n    const { personId } = connectionArgs;\n\n    // Create builder instance\n    const connectionBuilder = new PersonConnectionBuilder(connectionArgs);\n\n    // EXAMPLE: Count the total number of matching persons (without pagination)\n    const totalEdges = await countPersons({ where: { personId } });\n\n    // EXAMPLE: Do whatever you need to do to fetch the current page of persons\n    const persons = await fetchPersons({\n      where: { personId },\n      take: connectionBuilder.edgesPerPage, // how many rows to fetch\n      skip: connectionBuilder.startOffset, // row offset to start at\n    });\n\n    // Return resolved PersonConnection with edges and pageInfo\n    return connectionBuilder.build({\n      totalEdges,\n      nodes: persons,\n    });\n  }\n}\n```\n\n## Advanced Topics\n\n### Enriching Edges with additional metadata\n\nThe previous examples are sufficient for resolving connections that represent simple lists of objects with pagination.\nHowever, sometimes you need to model connections and edges that contain additional metadata. For example, you might\nrelate `Person` objects together into networks of friends using a `PersonFriendConnection` containing `PersonFriendEdge`\nedges. In this case the `node` on each edge is still a `Person` object, but the relationship itself may have\nproperties -- such as the date that the friend was added, or the type of relationship. (In relational database terms\nthis is analogous to having a Many-to-Many relation where the intermediate join table contains additional data columns\nbeyond just the keys of the two joined tables.)\n\nIn this case your edge type would look like the following example. Notice that we also now define a\n`PersonFriendEdgeInterface` type which we pass as a generic argument to `createEdgeType`; this ensures correct typings\nfor the fields that are allowed to be passed to your edge class's constructor for initialization when doing\n`new PersonFriendEdge({ ...fields })`.\n\n```ts\nimport { Field, GraphQLISODateTime, ObjectType } from '@nestjs/graphql';\nimport { createEdgeType, EdgeInterface } from 'nestjs-graphql-connection';\nimport { Person } from './entities';\n\nexport interface PersonFriendEdgeInterface extends EdgeInterface\u003cPerson\u003e {\n  createdAt: Date;\n}\n\n@ObjectType()\nexport class PersonFriendEdge\n  extends createEdgeType\u003cPersonFriendEdgeInterface\u003e(Person)\n  implements PersonFriendEdgeInterface\n{\n  @Field(type =\u003e GraphQLISODateTime)\n  public createdAt: Date;\n}\n```\n\nTo achieve this, you can pass an array of partial `edges` (instead of `nodes`) to `build()`. This enables you to\nprovide values for any additional fields present on the edges.\n\nThe following example assumes you have a GraphQL schema that defines a `friends` field on your `Person` object, which\nresolves to a `PersonFriendConnection` containing the person's friends. In your database you would have a `friend` table\nthat relates a `person` to an `otherPerson`, and that relationship has a `createdAt` date.\n\n```ts\nimport { Args, ResolveField, Resolver, Root } from '@nestjs/graphql';\n\n@Resolver(of =\u003e Person)\nexport class PersonResolver {\n  @ResolveField(returns =\u003e PersonFriendConnection)\n  public async friends(\n    @Root() person: Person,\n    @Args() connectionArgs: PersonFriendConnectionArgs,\n  ): Promise\u003cPersonFriendConnection\u003e {\n    // Create builder instance\n    const connectionBuilder = new PersonFriendConnectionBuilder(connectionArgs);\n\n    // EXAMPLE: Count the total number of this person's friends (without pagination)\n    const totalEdges = await countFriends({ where: { personId: person.id } });\n\n    // EXAMPLE: Do whatever you need to do to fetch the current page of this person's friends\n    const friends = await fetchFriends({\n      where: { personId: person.id },\n      take: connectionBuilder.edgesPerPage, // how many rows to fetch\n    });\n\n    // Return resolved PersonFriendConnection with edges and pageInfo\n    return connectionBuilder.build({\n      totalEdges,\n      edges: friends.map(friend =\u003e ({\n        node: friend.otherPerson,\n        createdAt: friend.createdAt,\n      })),\n    });\n  }\n}\n```\n\nAlternatively, you can override the `createEdge()` or `createConnection()` methods when calling `build()`.\n\n```ts\nreturn connectionBuilder.build({\n  totalEdges,\n  nodes: friends.map(friend =\u003e friend.otherPerson),\n  createConnection({ edges, pageInfo }) {\n    return new PersonFriendConnection({ edges, pageInfo, customField: 'hello-world' });\n  },\n  createEdge: ({ node, cursor }) =\u003e {\n    const friend = friends.find(friend =\u003e friend.otherPerson.id === node.id);\n\n    return new PersonFriendEdge({ node, cursor, createdAt: friend.createdAt });\n  },\n});\n```\n\nFinally, if the above methods don't meet your needs you can always build the connection result yourself by replacing\n`connectionBuilder.build(...)` with something like the following:\n\n```ts\n// Resolve edges with cursor, node, and additional metadata\nconst edges = friends.map(\n  (friend, index) =\u003e\n    new PersonFriendEdge({\n      cursor: connectionBuilder.createCursor(friend.otherPerson, index),\n      node: friend.otherPerson,\n      createdAt: friend.createdAt,\n    }),\n);\n\n// Return resolved PersonFriendConnection\nreturn new PersonFriendConnection({\n  pageInfo: connectionBuilder.createPageInfo({\n    edges,\n    totalEdges,\n    hasNextPage: true,\n    hasPreviousPage: false,\n  }),\n  edges,\n});\n```\n\n### Customising Cursors\n\nWhen using cursors for pagination of connections that allow the client to choose from different sorting options, you may\nneed to customise your cursor to reflect the chosen sort order. For example, if the client can sort `PersonConnection`\nby either name or creation date, the cursors you create on each edge will need to be different. It is no use knowing the\ncreation date of the last node if you are trying to fetch the next page of edges after the name \"Smith\", and vice versa.\n\nYou _could_ set the node ID as the cursor in all cases and simply look up the relevant data (name or creation date) from\nthe node when given such a cursor. However, if you have a dataset that could change between requests then this approach\nintroduces the potential for odd behavior and/or missing results.\n\nImagine you have a `sortOption` field on your `PersonConnectionArgs` that determines the requested sort order:\n\n```ts\n@ArgsType()\nexport class PersonConnectionArgs extends ConnectionArgs {\n  // In reality you might want an enum here, but we'll use a string for simplicity\n  @Field(type =\u003e String, { nullable: true })\n  public sortOption?: string;\n}\n```\n\nYou can customise your cursor based on the `sortOption` from the `ConnectionArgs` by changing your definition of\n`createCursor` and `decodeCursor` in your builder class like the following example:\n\n```ts\nexport class PersonConnectionBuilder extends ConnectionBuilder\u003c\n  PersonConnection,\n  PersonConnectionArgs,\n  PersonEdge,\n  Person,\n  PersonCursor\n\u003e {\n  // ... (methods createConnection and createEdge remain unchanged)\n\n  public createCursor(node: Person): PersonCursor {\n    if (this.connectionArgs.sortOption === 'name') {\n      return new Cursor({ name: node.name });\n    }\n\n    return new Cursor({ createdAt: node.createdAt.toISOString() });\n  }\n\n  public decodeCursor(encodedString: string): PersonCursor {\n    if (this.connectionArgs.sortOption === 'name') {\n      return Cursor.fromString(encodedString, params =\u003e\n        validateParamsUsingSchema(\n          params,\n          Joi.object({\n            name: Joi.string().empty('').required(),\n          }).unknown(false),\n        ),\n      );\n    }\n\n    return Cursor.fromString(encodedString, params =\u003e\n      validateParamsUsingSchema(\n        params,\n        Joi.object({\n          id: Joi.string().empty('').required(),\n        }).unknown(false),\n      ),\n    );\n  }\n}\n```\n\nAlternatively, `ConnectionBuilder` supports overriding the `createCursor()` method when calling `build()`. So you could\nalso do it like this:\n\n```ts\nimport { Args, ResolveField, Resolver, Root } from '@nestjs/graphql';\n\n@Resolver()\nexport class PersonQueryResolver {\n  @Query(returns =\u003e PersonConnection)\n  public async persons(@Args() connectionArgs: PersonConnectionArgs): Promise\u003cPersonConnection\u003e {\n    const { sortOption } = connectionArgs;\n\n    // Create builder instance\n    const connectionBuilder = new PersonConnectionBuilder(connectionArgs);\n\n    // EXAMPLE: Do whatever you need to do to fetch the current page of persons using the specified sort order\n    const persons = await fetchPersons({\n      where: { personId },\n      order: sortOption === 'name' ? { name: 'ASC' } : { createdAt: 'ASC' },\n      take: connectionBuilder.edgesPerPage, // how many rows to fetch\n    });\n\n    // Return resolved PersonConnection with edges and pageInfo\n    return connectionBuilder.build({\n      totalEdges: await countPersons(),\n      nodes: persons,\n      createCursor(node) {\n        return new Cursor(sortOption === 'name' ? { name: node.name } : { createdAt: node.createdAt.toISOString() });\n      },\n    });\n  }\n}\n```\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fequalogic%2Fnestjs-graphql-connection","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fequalogic%2Fnestjs-graphql-connection","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fequalogic%2Fnestjs-graphql-connection/lists"}