A collection of utilities for building real-world GraphQL apps using NestJS.
- "Relay-style" pagination using connections, edges, and nodes that follows the Cursor Connections Specification.
- Batch loading of records using DataLoader.
Assume we have the following GraphQL schema
@ObjectType()
class TodoList {
@Field(type => ID)
public id: string;
@Field()
public name: string;
@Field(type => [TodoItem])
public items: TodoItem[];
}
@ObjectType()
class TodoItem {
@Field(type => ID)
public id: string;
@Field()
public description: string;
@Field(type => TodoList)
public list: TodoList;
}
@Resolver(TodoItem)
class TodoItemResolver {
constructor(private readonly service: TodoService) {}
@Query(returns => [TodoItem])
public async items() {
return this.service.getAllItems();
}
}
We can create a connection class by extending Connection
and use it on the items
field in our TodoList
type
import { Connection } from 'nest-graphql-utils';
@ObjectType()
class TodoItemConnection extends Connection(TodoItem) {}
@ObjectType()
class TodoList {
@Field(type => ID)
public id: string;
@Field()
public name: string;
@Field(type => TodoItemConnection)
public items: TodoItemConnection;
}
And update the TodoListResolver
to create and return the connection, utilizing the createConnection
function
import { createConnection, PaginationArgs } from 'nest-graphql-utils';
@Resolver(TodoItem)
class TodoItemResolver {
constructor(private readonly service: TodoService) {}
@Query(returns => TodoItemConnection)
public async items(@Args() paginationArgs: PaginationArgs): Promise<TodoItemConnection> {
return createConnection({
paginationArgs,
paginate: args => this.service.paginateItems(args.offset, args.limit),
});
}
}
We can then query the todo items like this
query {
items(after: "ABCDE==", first: 3) {
totalCount
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
id
description
}
}
}
}
Which might return a result such as
{
"items": {
"totalCount": 50,
"pageInfo": {
"startCursor": "ABCDE==",
"endCursor": "FGHIJ==",
"hasNextPage": true,
"hasPreviousPage": true
},
"edges": [
{
"cursor": "ABCDE==",
"node": {
"id": 20,
"description": "Write the code"
}
},
{
"cursor": "ZHDHF==",
"node": {
"id": 21,
"description": "Delete the code"
}
},
{
"cursor": "FGHIJ==",
"node": {
"id": 22,
"description": "Try again"
}
}
]
}
}
Let's add a field resolver to our TodoItemResolver
@ResolveField(returns => TodoList)
public async list(@Parent() item: TodoItem) {
return this.service.getListById(item.listId);
}
We can use batch loading to avoid the N+1 query problem here. First, we need to define a new loader by extending BatchLoader
and overriding the load
method
import { DataLoaderFactory } from 'nest-graphql-utils';
@Injectable()
class TodoListLoader implements DataLoaderFactory<TodoList> {
constructor(private readonly service: TodoService) {}
create() {
return new DataLoader<number, TodoList>((keys: number[]) =>
this.service.getListsByIds(keys);
);
}
}
We can then update the resolveList
method in our TodoItemResolver
to use the new loader
@ResolveField(returns => TodoList)
public async list(
@Parent() item: TodoItem,
@Loader(TodoListLoader) loader: ReturnType<TodoListLoader['create']>,
) {
return loader.load(item.listId);
}
Finally, we need to provide the DataLoaderInterceptor
in order for the Loader
decorator to work. We do that by adding the provider to our app module
@Module({
providers: [
// any other providers that your app module uses,
DataLoaderInterceptorProvider,
],
})
export class AppModule {}
- Support Nest
8.4.4
- Remove
connectionClass
fromcreateConnection
options
- Add
offset
pagination argument
- Export all decorators
- Add federation decorators
- Fix endCursor pointing to next cursor rather than last cursor
- Support request scoped injection in loaders and bump peer dependencies
- Allow custom naming of connection and edge
- Replace
BatchLoader
withDataLoaderFactory
to only load per request. Default key type changed to string
- Update
Loader
decorator to work with Nest 7 changes tocreateParamDecorator
- Fix
Connection.edges
field for Nest 7 support
- Support for NestJS 7
- Add
DataLoaderInterceptor
interceptor andLoader
decorator to fix batch loading
- Initial release