Gathering detailed insights and metrics for nestjs-decorated-dataloaders
Gathering detailed insights and metrics for nestjs-decorated-dataloaders
Gathering detailed insights and metrics for nestjs-decorated-dataloaders
Gathering detailed insights and metrics for nestjs-decorated-dataloaders
A module that helps you to create graphql dataloaders with decorators in Nest.js
npm install nestjs-decorated-dataloaders
Typescript
Module System
Node Version
NPM Version
TypeScript (100%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
10 Stars
48 Commits
1 Forks
1 Watchers
3 Branches
1 Contributors
Updated on May 19, 2025
Latest Version
1.1.0
Package Id
nestjs-decorated-dataloaders@1.1.0
Unpacked Size
72.65 kB
Size
17.80 kB
File Count
85
NPM Version
10.9.2
Node Version
22.15.1
Published on
May 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
5
31
A lightweight wrapper around Dataloader that lets you declare where to batch and cache instead of wiring it by hand. Add a @Load decorator to any field, register a handler, and the N+1 query problem is gone.
1npm install nestjs-decorated-dataloaders
or using yarn:
1yarn add nestjs-decorated-dataloaders
Configure the DataloaderModule
in your application module:
1import { Module } from "@nestjs/common"; 2import { GraphQLModule } from "@nestjs/graphql"; 3import { LRUMap } from "lru_map"; 4import { DataloaderModule } from "nestjs-decorated-dataloaders"; 5 6@Module({ 7 imports: [ 8 GraphQLModule.forRoot({ 9 autoSchemaFile: true, 10 }), 11 DataloaderModule.forRoot({ 12 name: "MyAwesomeDataloader", 13 cache: true, 14 maxBatchSize: 100, 15 getCacheMap: () => new LRUMap(100), 16 }), 17 ], 18}) 19export class AppModule {}
name
: Names the dataloader for better tracking and debugging.cache
: Enables caching.maxBatchSize
: Limits the maximum number of batched requests.getCacheMap
: Defines a custom cache implementation (e.g., LRU Cache).1import { Field, Int, ObjectType } from "@nestjs/graphql"; 2import { Load } from "nestjs-decorated-dataloaders"; 3import { UserEntity } from "./user.entity"; 4 5@ObjectType() 6export class PhotoEntity { 7 @Field(() => Int) 8 id: number; 9 10 @Field(() => String) 11 url: string; 12 13 @Field(() => Number) 14 userId: number; 15}
1import { Field, Int, ObjectType } from "@nestjs/graphql"; 2import { Load } from "nestjs-decorated-dataloaders"; 3import { PhotoEntity } from "./photo.entity"; 4 5@ObjectType() 6export class UserEntity { 7 @Field(() => Int) 8 id: number; 9 10 @Field(() => String) 11 name: string; 12 13 @Field(() => Date) 14 createdAt: Date; 15 16 // One-to-one relationship with PhotoEntity 17 @Load(() => PhotoEntity, { key: "id", parentKey: "userId", handler: "LOAD_PHOTOS_BY_USER_ID" }) 18 photo: PhotoEntity; 19 20 // One-to-many relationship with PhotoEntity 21 @Load(() => [PhotoEntity], { key: "id", parentKey: "userId", handler: "LOAD_PHOTOS_BY_USER_ID" }) 22 photos: PhotoEntity[]; 23}
Dataloader handlers define how data is fetched from the data source. Handlers are tied to specific dataloaders using the @DataloaderHandler
decorator.
1import { Inject, Injectable } from "@nestjs/common"; 2import { DataloaderHandler } from "nestjs-decorated-dataloaders"; 3import { PhotoEntity } from "../../entities/photo.entity"; 4import { DatabaseService } from "../database/database.service"; 5 6// tip: define constants for handler keys in a separated file 7export const LOAD_PHOTOS_BY_USER = "LOAD_PHOTOS_BY_USER"; 8 9@Injectable() 10export class PhotoRepository { 11 constructor( 12 @Inject(DatabaseService) 13 private readonly database: DatabaseService, 14 ) {} 15 16 /** 17 * This method will be called by the dataloader with batched user IDs 18 */ 19 @DataloaderHandler(LOAD_PHOTOS_BY_USER) 20 async findAllByUsersIds(usersIds: number[]): Promise<PhotoEntity[]> { 21 // Fetch all photos from some data source 22 const photos = await this.database.getPhotos(); 23 24 // Filter photos by the batch of user IDs 25 return photos.filter((photo) => usersIds.includes(photo.userId)); 26 } 27}
Resolvers use the DataloaderService
to load related entities, ensuring requests are batched and cached.
1import { Inject } from "@nestjs/common"; 2import { Parent, ResolveField, Resolver } from "@nestjs/graphql"; 3import { DataloaderService } from "nestjs-decorated-dataloaders"; 4import { GroupEntity } from "../entities/group.entity"; 5import { PhotoEntity } from "../entities/photo.entity"; 6import { UserEntity } from "../entities/user.entity"; 7 8@Resolver(() => UserEntity) 9export class UserResolver { 10 constructor( 11 @Inject(DataloaderService) 12 private readonly dataloaderService: DataloaderService, 13 ) {} 14 15 /** 16 * This resolver field uses the dataloader to fetch photos for a user 17 * The dataloader will batch and cache requests for optimal performance 18 */ 19 @ResolveField(() => [PhotoEntity]) 20 async photos(@Parent() user: UserEntity) { 21 return this.dataloaderService.load({ 22 from: UserEntity, 23 field: "photos", 24 data: user 25 }); 26 } 27}
Function-Based Mapper allows you to use functions instead of string paths for the key
and parentKey
properties in the @Load
decorator. This is particularly useful when you need to work with composite keys or when you need more complex mapping logic.
1import { Field, Int, ObjectType } from "@nestjs/graphql"; 2import { Load } from "nestjs-decorated-dataloaders"; 3import { CategoryPostEntity } from "./category-post.entity"; 4import { CategoryEntity } from "./category.entity"; 5 6@ObjectType() 7export class PostEntity { 8 @Field(() => Int) 9 id: number; 10 11 @Field(() => String) 12 title: string; 13 14 @Field(() => String) 15 content: string; 16 17 @Field(() => String) 18 createdAt: string; 19 20 // Relationship with CategoryPostEntity for the many-to-many relationship 21 categoryPosts: CategoryPostEntity[]; 22 23 /** 24 * Using Function-Based Mapper for complex relationships 25 * This handles a many-to-many relationship through a join table 26 */ 27 @Load(() => [CategoryEntity], { 28 key: (category) => category.id, 29 parentKey: (post) => post.categoryPosts.map((cp) => cp.postId), 30 handler: "LOAD_CATEGORY_BY_POSTS", 31 }) 32 categories: CategoryEntity[]; 33}
In this example, the key
function extracts the id
from the category entity, and the parentKey
function maps through the categoryPosts
array to extract all postId
values.
You can use TypeScript generics to ensure type safety when declaring a Dataloader field.
1import { Field, Int, ObjectType } from "@nestjs/graphql"; 2import { Load } from "nestjs-decorated-dataloaders"; 3import { PhotoEntity } from "./photo.entity"; 4 5@ObjectType() 6export class UserEntity { 7 @Field(() => Int) 8 id: number; 9 10 @Field(() => String) 11 name: string; 12 13 @Field(() => Date) 14 createdAt: Date; 15 16 @Load<PhotoEntity, UserEntity>(() => [PhotoEntity], { 17 key: (user) => user.id, 18 parentKey: (photo) => photo.userId, 19 handler: "LOAD_PHOTOS_BY_USER", 20 }) 21 photos: Array<PhotoEntity>; 22}
In this example, the key
function is typed to receive a UserEntity
and the parentKey
function is typed to receive a PhotoEntity
.
Circular dependencies between entities (e.g., User ↔ Photo) can cause metadata resolution errors when using reflect-metadata. For example:
reflect-metadata tries to read metadata from User, which references Photo.
Photo in turn references User, but if User hasn't been fully initialized, its metadata resolves to undefined.
This issue is common in environments using SWC. To resolve it, use the Relation
Solution: Wrapping Circular References
Encapsulate circular properties with Relation
Example:
1 2import { Relation } from 'nestjs-decorated-dataloaders'; 3 4class User { 5 photo: Relation<Photo>; 6} 7 8class Photo { 9 user: Relation<User>; 10}
How It Works
Generic Type Erasure: reflect-metadata cannot infer generic types like Relation
Explicit Type Declaration: You must manually specify the wrapped type (e.g., Relation
Important Notes
Use Relation
Aliases let you associate a dataloader handler with an abstract class, offering a simple way to handle cases where decorators can't be used, especially in complex architectures with shared or abstract classes.
1@AliasFor(() => AbstractPhotoService) 2export class ConcretePhotoService {}
This allows PhotoService
to serve as the dataloader handler for AbstractPhotoService
.
nestjs-decorated-dataloaders
is built on top of the GraphQL Dataloader library. At its core, a dataloader is a mechanism for batching and caching database or API requests, reducing the number of round trips required to fetch related data.
nestjs-decorated-dataloaders
abstracts the complexities of manually managing dataloaders and integrates seamlessly with Nest.js using decorators. It provides a declarative and maintainable approach to solving the N+1 problem, allowing you to focus on building features without worrying about the underlying dataloader logic.
By using decorators like @Load
and @DataloaderHandler
, this module streamlines dataloader setup, making it simple to handle related entities in GraphQL resolvers without manual dataloader instantiation or dependency injection.
No vulnerabilities found.
No security vulnerabilities found.