@azhulin/pagination-nestjs-graphql
v1.2.0
Published
NestJS code-first GraphQL connection types for @azhulin/pagination-core.
Maintainers
Readme
Pagination (NestJS GraphQL)
NestJS code-first GraphQL types for @azhulin/pagination-core connections — connection, edge, page-info,
and the connection arguments, wired to the core shapes.
npm i @azhulin/pagination-nestjs-graphqlPeer dependencies: @nestjs/graphql, @nestjs/common, class-validator, reflect-metadata.
Exports
| Export | Kind | Use |
|----------------------------|-------------------------|----------------------------------------------------------------|
| ConnectionOutput(Edge) | @ObjectType mixin | Build a connection type for an edge type. |
| ConnectionEdgeOutput(N) | @ObjectType mixin | Build an edge type for a node type. |
| ConnectionPageInfoOutput | @ObjectType | Page-info type; registers as schema type ConnectionPageInfo. |
| ConnectionArgsInput | @ArgsType | The validated connection arguments as nullable GraphQL fields. |
| PaginationModeEnum | registered GraphQL enum | The core PaginationMode, registered for the schema. |
Define the types
GraphQL output classes carry an Output suffix and register their schema name without it via the @ObjectType(name)
argument (UserOutput → User), keeping them distinct from the domain User the paginator returns. Build the edge and
connection with the mixin functions, mapping the framework-neutral node up through each constructor:
import type { Connection, ConnectionEdge } from '@azhulin/pagination-core'
import { ArgsType, Field, ObjectType } from '@nestjs/graphql'
import { IsNotEmpty } from 'class-validator'
import { ConnectionArgsInput, ConnectionEdgeOutput, ConnectionOutput } from '@azhulin/pagination-nestjs-graphql'
import { User } from './user.model' // your domain entity — the node the paginator emits
@ObjectType('User')
export class UserOutput {
@Field()
public readonly id!: string
public constructor(user: User) {
this.id = user.id
}
}
@ObjectType('UserEdge')
export class UserEdgeOutput extends ConnectionEdgeOutput(UserOutput) {
public constructor(edge: ConnectionEdge<User>) {
super(edge.cursor, new UserOutput(edge.node))
}
}
@ObjectType('UserConnection')
export class UserConnectionOutput extends ConnectionOutput(UserEdgeOutput) {
public constructor(connection: Connection<ConnectionEdge<User>>) {
super(connection.pageInfo, connection.edges.map((edge) => new UserEdgeOutput(edge)))
}
}
// Add your own filters — ConnectionArgsDto validation comes along.
@ArgsType()
export class UserConnectionArgs extends ConnectionArgsInput {
@Field()
@IsNotEmpty()
public readonly groupId!: string
}The constructors are only there to map the node; when the node the paginator emits is already your GraphQL
@ObjectType, extend with an empty body (class UserEdgeOutput extends ConnectionEdgeOutput(UserOutput) {}).
The page-info type and enum register themselves, producing:
type UserConnection {
pageInfo: ConnectionPageInfo!
edges: [UserEdge!]!
}
type UserEdge {
cursor: String!
node: User!
}
type ConnectionPageInfo {
mode: PaginationMode!
hasPreviousPage: Boolean!
hasNextPage: Boolean!
startCursor: String
endCursor: String
totalCount: Int
page: Int # offset mode only
pageSize: Int # offset mode only
totalPages: Int # offset mode only
}
enum PaginationMode {
Cursor
Offset
}Resolver
The UserConnectionOutput constructor maps the framework-neutral Connection onto the GraphQL types, so the resolver
stays thin — no pagination logic:
import { Args, Query, Resolver } from '@nestjs/graphql'
@Resolver()
export class UserResolver {
public constructor(private readonly users: UserRepository) {}
@Query(() => UserConnectionOutput)
public async usersByGroup(@Args() args: UserConnectionArgs): Promise<UserConnectionOutput> {
// The repository builds a framework-neutral Connection<User> with a core Paginator (+ an adapter such as
// @azhulin/pagination-typeorm); the constructor maps it onto the GraphQL types.
return new UserConnectionOutput(await this.users.paginateByGroup(args, args.groupId))
}
}Clients send after/before/first/last or page/pageSize, plus your own arguments:
query {
usersByGroup(groupId: "g1", first: 20, after: "eyJlbWFpbCI6…") {
pageInfo { hasNextPage endCursor mode }
edges { cursor node { id email } }
}
}Extra edge / connection fields
Widen a type, then populate it by overriding the matching core hook (createConnectionEdge / createConnection) on
your Paginator subclass — or with a standard @ResolveField:
@ObjectType('UserEdge')
export class UserEdgeOutput extends ConnectionEdgeOutput(UserOutput) {
@Field()
public readonly highlighted!: boolean
}License
MIT © 2022–2026 Alex Zhulin
