Open zuohuadong opened 6 years ago
You probably wanna start with Interceptors.
@twilroad Nope, but link above is basically everything you need to get started. Interceptor can be used for caching (you can see it in docs), but with slight modifications you can store result from next handler. As a param, you will need a cache key, you can pass it like shown here https://docs.nestjs.com/advanced/mixins.
export class ApplicationModule implements NestModule {
constructor(
private readonly articleService: ArticleService,
private readonly graphQLFactory: GraphQLFactory,
private readonly userService: UserService,
private readonly cinemaClassicsService: CinemaClassicsService,
private readonly artformCategoryService: ArtformCategoryService,
private readonly configService: ConfigService,
private readonly geoChapterService: GeoChapterService,
private readonly downloadCategoryService: DownloadCategoryService,
private readonly artformService: ArtformService,
private readonly personalProfileService: PersonalProfileService,
private readonly artistProfileService: ArtistProfileService,
private readonly artistListService: ArtistListService,
) {}
configure(consumer: MiddlewareConsumer) {
const awsSettings = this.configService.getAWSConfig();
const disableCors = this.configService.disableCors();
let headers: {
'Access-Control-Allow-Origin'?: string;
} = {};
if (disableCors) {
headers = { 'Access-Control-Allow-Origin': 'http://localhost:3010' };
}
consumer
.apply(
require('react-s3-uploader/s3router')({
bucket: awsSettings.defaultBucket,
region: awsSettings.defaultRegion,
signatureVersion: 'v4',
headers,
ACL: 'public-read',
uniquePrefix: true,
}),
)
.forRoutes('/s3');
consumer.apply(AuthMiddleware).forRoutes('/health/anonymous');
consumer.apply(AuthMiddleware).forRoutes('/health/authenticated');
consumer.apply(AuthMiddleware).forRoutes('/query');
const typeDefs = this.graphQLFactory.mergeTypesByPaths('./**/*.graphql', './../node_modules/@ankitbabbar/instiapp-api-core/src/**/*.graphql');
const resolvers = {
DateTime: GraphQLDateTime,
Date: GraphQLDate,
Time: GraphQLTime,
};
const schema = this.graphQLFactory.createSchema({
typeDefs,
resolvers,
resolverValidationOptions: {
requireResolversForResolveType: false,
},
});
consumer
.apply(
graphqlExpress((req, res) => {
const viewer = res.locals.viewer;
const dataLoaders: ContribDataLoaders = {
user: new DataLoader((ids: number[]) => this.userService.getManyByIds(viewer, ids)),
article: new DataLoader((ids: number[]) => this.articleService.getManyByIds(viewer, ids)),
cinemaClassics: new DataLoader((ids: number[]) => this.cinemaClassicsService.getManyByIds(viewer, ids)),
artform: new DataLoader((ids: number[]) => this.artformService.getManyByIds(viewer, ids)),
geoChapter: new DataLoader((ids: number[]) => this.geoChapterService.getManyByIds(viewer, ids)),
artformCategory: new DataLoader((ids: number[]) => this.artformCategoryService.getManyByIds(viewer, ids)),
downloadCategory: new DataLoader((ids: number[]) => this.downloadCategoryService.getManyByIds(viewer, ids)),
personalProfile: new DataLoader((ids: number[]) => this.personalProfileService.getManyByIds(viewer, ids)),
artistList: new DataLoader((ids: number[]) => this.artistListService.getManyByIds(viewer, ids)),
artistProfile: new DataLoader((ids: number[]) => this.artistProfileService.getManyByIds(viewer, ids)),
};
const context: IGQLContext = {
viewer,
dataLoaders,
};
return {
schema,
rootValue: req,
formatError,
context,
};
}),
)
.forRoutes('/query');
}
}
How would this work with the new GraphQLModule.forRoot({...options})? Where should we initialize the context? Maybe creating a wrapper module and doing it on configure(consumer) somehow? It should be a different instance of data loader(s) for each different request.
How would this work with the new GraphQLModule.forRoot({...options})? Where should we initialize the context? Maybe creating a wrapper module and doing it on configure(consumer) somehow? It should be a different instance of data loader(s) for each different request.
take a look at this approach https://github.com/nestjs/graphql/issues/2#issuecomment-439650698
@kamilmysliwiec Dataloader is supposed to batch all the resolved entity ids and query them in one go as a single SQL query.
But for some reason when used in Nest it executes more than one query for a single request.
Here is an example
Suppose we have a following GQL query
query {
persons {
name
address {
street
}
}
}
So addresses for all the persons from this query should be fetched using a single SQL query (using dataloader approach) but in reality, I have something like this:
1) All the persons fetched
2) Dataloader collects and loads address ids for persons 1
and 2
(SQL query 1)
3) Then for person 3
(SQL query 2)
4) Then for persons 4
and 5
(SQL query 3)
I know that dataloader should be invoked on a nextTick
but looks like there is something in a way NestJS implements resolvers.
Any help is appreciated, maybe I just missed something regarding the dataloader..
I think this is related https://github.com/graphql/dataloader/issues/150
For those who faced the same issue, I've mentioned above.
It was caused by the fact that on every GQL property resolution Nest invokes my Auth global guard which also does lots of async stuff, interfering with dataloader
's execution.
I've added a flag that checks whether a user is already authenticated in the guard in order to prevent its redundant executions.
@kamilmysliwiec Is this expected behavior that guards (probably interceptors, filters, etc. as well) get executed on every GQL property resolution?
@kamilmysliwiec Is this expected behavior that guards (probably interceptors, filters, etc. as well) get executed on every GQL property resolution?
@rychkog this is fixed in @nestjs/graphql@next
already. I'll publish this release as soon as possible.
@kamilmysliwiec maybe this could be somewhat optional? you disabled all guards, filters and interceptors. what if I want to run guard againts some property?
In my case I was running interceptor altering the context and this was breaking change. I think this ability could be actually in ResolveProperty options or something, especially guards should be allowed.
@trubit it would have a big impact on the performance. I'd suggest moving this logic inside the resolve property handler.
I agree with @trubit , it's a breaking change.
For example, I use guard to control access to my properties, with a AuthGuard and RoleGuard. Some properties are like "query", with filter, pagination etc... and need the same rules as query or mutation.
So now, we will have 2 different implementations to check roles and auth ...
I imagine this line https://github.com/nestjs/graphql/blob/1504e021af3119feb8d8ecaed29b47d695939d9c/lib/services/resolvers-explorer.service.ts#L134 could be somehow referenced in @ResolveProperty's decorator options. At least making it possible to add guards on properties. Of course it takes some performance hits, was it so big?
@zuohuadong How I do it is I use request-scoped providers that create instances of injectable services that have dataloaders in them.
export interface IDataLoader<K, V> {
load(id: K): Promise<V>;
}
import DataLoader from 'dataloader';
import { UserEntity } from '../user.entity';
import { IDataLoader } from 'src/api/common/interfaces/data-loader.interface';
import { Connection } from 'typeorm';
export class UsersDataLoader implements IDataLoader<string, UserEntity> {
constructor(private readonly dataLoader: DataLoader<string, UserEntity>) {}
public static async create(connection: Connection): Promise<UsersDataLoader> {
const usersRepo = connection.getRepository<UserEntity>(UserEntity);
const dataLoader = new DataLoader<string, UserEntity>(async keys => {
const loadedEntities = await usersRepo.findByIds(keys);
return keys.map(key => loadedEntities.find(entity => entity.id === key)); // sort by keys
});
return new UsersDataLoader(dataLoader);
}
public async load(id: string) {
return this.dataLoader.load(id);
}
}
import { Provider, Scope } from '@nestjs/common';
import { Types } from 'src/common/types';
import { UsersDataLoader } from './users.data-loader';
export const usersDataLoaderProvider: Provider = {
inject: [Types.DATABASE_CONNECTION],
useFactory: UsersDataLoader.create,
provide: UsersDataLoader,
scope: Scope.REQUEST,
};
Then you simply inject it as a regular service in the resolver.
@Resolver(of => PostType)
export class PostResolver {
constructor(
private readonly usersDataLoader: UsersDataLoader,
) {}
@ResolveProperty('author')
public async author(@Parent() post: PostEntity) {
return this.usersDataLoader.load(post.authorId);
}
}
This way you can also use the dataloaders anywhere in the code, like in service classes, not only in resolver methods.
@vnenkpet this throws an error Nest can't resolve dependencies of the UserDataLoader (?).
it took me like 2 days debugging how to use DataLoader with Nestjs, and finally I could make it. so here is what I did perhaps this might help someone else in the future.
first create your contract as @vnenkpet did.
export interface IDataLoader<K, V> {
load(id: K): Promise<V>;
}
then create your user loader for example.
import { Injectable, Inject, forwardRef } from "@nestjs/common";
import { ModuleRef } from "@nestjs/core";
import { UserDTO } from "@commerce/shared";
import DataLoader = require("dataloader"); // commonjs module
import { IDataLoader } from "../contracts/nest-dataloader";
import { UserService } from "../users/user.service";
@Injectable()
// you can replace the UserDTO with your entity or whatever, just pass what you want to accept and return in the end.
export class UserDataLoader implements IDataLoader<string, UserDTO> {
constructor(private readonly dataLoader: DataLoader<any, any>) {}
public static async create(
userService: UserService // injecting the user service as I'm going to use it to fetch the users by ids
): Promise<UserDataLoader> {
// your Dataloader, which again accepts the ids and return whatever you want.
const dataloader = new DataLoader<string, UserDTO>(async ids => {
let users = await userService.fetchUsersByIds(ids);
// after having the response, map the ids you already have with the users you got from the service by ids. So Dataloader can navigate later.
return ids.map(key => users.find(entity => entity.id === key));
});
return new UserDataLoader(dataloader);
}
public async load(id: string) {
return this.dataLoader.load(id);
}
}
In my scenario, I had to use this userService at another module, so here is the code for that.
at your users module you must put that service at providers property in order to work, and you have to export it from there as you are going to use it in another module ( so you can import it from there )
// at your users module.
@Module({
providers: [UserService, UserResolver],
exports: [UserService]
})
at the other module you are trying to use the loader at ( which in my case was products module and I needed to fetch users associated with these products.)
// at your products module or whatever module.
@Module({
providers: [
ProductResolver,
ProductService,
{
inject: [UserService],
useFactory: UserDataLoader.create,
provide: UserDataLoader,
scope: Scope.REQUEST
}
],
imports: [UsersModule]
})
then finally at ProductResolver, I injected it.
constructor(
private readonly usersDataLoader: UserDataLoader
) {}
then when resolving the property at the same resolver.
@ResolveProperty("user", () => UserDTO)
async user(@Parent() product: ProductDTO): Promise<UserDTO> {
return this.usersDataLoader.load(product.user.id);
}
I came up with the following that seems to work nicely.
fixture.module.ts
@Module({
imports: [TypeOrmModule.forFeature([FixtureRepository])],
providers: [FixtureService, FixtureLoaders, FixtureResolvers],
})
export class FixtureModule {}
fixture.loaders.ts
@Injectable({ scope: Scope.REQUEST })
export class FixtureLoaders {
constructor(private readonly fixtureService: FixtureService) {}
public readonly findByCode = new DataLoader<number, Fixture>(async codes => {
try {
const fixtures = await this.fixtureService.findByCodes(codes)
return codes.map(code => fixtures.find(fixture => fixture.code === code))
} catch (error) {
throw error
}
})
}
fixture.resolvers.ts
@Injectable()
export class FixtureResolvers {
constructor(private readonly fixtureLoaders: FixtureLoaders) {}
@Query('getFixtureByCode')
async getFixtureByCode(
@Args('code') code: number,
): Promise<FixtureRO | undefined> {
try {
const fixture = await this.fixtureLoaders.findByCode.load(code)
if (fixture !== undefined) {
return fixture.toResponseObject()
} else {
return undefined
}
} catch (error) {
throw error
}
}
}
@secmohammed @jeppe-smith For some reason, your solution doesn't fix the n+1 problem, I've enabled the query logger in mysql and I can see that the fetch queries for the related objects are fired one by one. If I remove the Scope.REQUEST, then the queries are executed once, but the loader cache keeps the results until the app closes, this will be problematic if we are loading a huge amount of related objects (since the objects are saved on memory), we could end up loading the whole database in memory...
Dataloader is designed to end up the cached version by the end of the request. It was designed to do so. this will help you to batch a multiple requests with the ids and by the end of the promise fetch all of the requests at once. you can integrate with a Redis server or so to accomplish having a cached version of your data. Also, Having a N+1 is basically based on your sql query you are trying to do, it should be where id is in (...your ids), and this one will be batched at the very last of your promise to fetch them at once. However, I don't know how Scope.Request makes the change.
The problem seems be that the DataLoader is instantiated multiple times inside the request: on each result... The Scope.DEFAULT, obviously keep the same instance of DataLoader, since it's singleton...
I agree with @pablor21, I encountered the same problem with multiple instance by request. May be it's a bug with Nest. So, for now, I use this solution: https://github.com/nestjs/graphql/issues/2#issuecomment-498968008
@pablor21 How are you sending requests that cause multiple instances of the dataloader? If I send a query like
query {
first: getFixtureByCode(code: 855172) {
id
}
second: getFixtureByCode(code: 855172) {
id
}
third: getFixtureByCode(code: 855174) {
id
}
}
I get a single instance of dataloader and my database is queried once with { codes: [855172, 855174] }
as I would expect it to.
I created a small npm package, nestjs-dataloader, since I also struggled with this. Hopefully it helps someone else.
I struggled a lot trying to understand this, I'll just write my solution which probably isn't the cleanest but it's easiest for myself at least
PS: the "genericOneToManyDataLoader" can be of course custom made made for every field, I was just experimenting some generic way (btw, if someone can help me make it typesafe I would appreciate a lot, I don't know how to do it, now it just returns "any[]")
export const genericOneToManyDataLoader = (t: any, keyField: string) =>
new DataLoader<number, typeof t[]>(async (keys: number[]) => {
const items = await getRepository(t).find({
where: { [keyField]: In(keys) },
});
return keys.map(id => items.filter(el => el[keyField] === id));
});
export interface MyContext {
req: Request;
res: Response;
commentDataLoader: ReturnType<typeof genericOneToManyDataLoader>;
postDataLoader: ReturnType<typeof genericOneToManyDataLoader>;
}
@Module({
imports: [
PostModule,
CommentModule,
UserModule,
GraphQLModule.forRoot({
playground: true,
debug: true,
autoSchemaFile: true,
installSubscriptionHandlers: true,
context: ({ req, res }) => ({
req,
res,
commentDataLoader: genericOneToManyDataLoader(Comment, 'postId'),
postDataLoader: genericOneToManyDataLoader(Post, 'userId'),
}),
}),
DatabaseModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
// user.resolver.ts -- its literally the same in comments
@ResolveField(() => [Post], { nullable: 'items' })
async posts(@Parent() parent: User, @Context() ctx: MyContext) {
return ctx.postDataLoader.load(parent.id);
}
UPDATE: I needed services in loaders. and the above solution of mine was bad, I ended up using @jeppe-smith s one which worked very well It generated three queries (as expected) for a users -> posts -> comments (many -> many -> many) query, and to me it seems the easiest solution
Thank you a lot for that, I don't understand why others say this generates multiple dataloaders/queries, I get only one per request as expected
I came up with the following that seems to work nicely.
fixture.module.ts
@Module({ imports: [TypeOrmModule.forFeature([FixtureRepository])], providers: [FixtureService, FixtureLoaders, FixtureResolvers], }) export class FixtureModule {}
fixture.loaders.ts
@Injectable({ scope: Scope.REQUEST }) export class FixtureLoaders { constructor(private readonly fixtureService: FixtureService) {} public readonly findByCode = new DataLoader<number, Fixture>(async codes => { try { const fixtures = await this.fixtureService.findByCodes(codes) return codes.map(code => fixtures.find(fixture => fixture.code === code)) } catch (error) { throw error } }) }
fixture.resolvers.ts
@Injectable() export class FixtureResolvers { constructor(private readonly fixtureLoaders: FixtureLoaders) {} @Query('getFixtureByCode') async getFixtureByCode( @Args('code') code: number, ): Promise<FixtureRO | undefined> { try { const fixture = await this.fixtureLoaders.findByCode.load(code) if (fixture !== undefined) { return fixture.toResponseObject() } else { return undefined } } catch (error) { throw error } } }
This is what I've got:
import { Injectable, Scope } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import DataLoader from 'dataloader'
import { Repository } from 'typeorm'
import { Project } from './project.entity'
@Injectable({ scope: Scope.REQUEST })
export class ProjectLoader {
constructor(
@InjectRepository(Project)
private readonly projectRepository: Repository<Project>,
) {}
readonly findOneByIds = new DataLoader<string, Project | undefined>(async (ids) => {
const projects = await this.projectRepository
.createQueryBuilder('project')
.where('project.id IN (:...ids)', { ids })
.getMany()
return ids.map((id) => projects.find((n) => n.id === id))
})
}
Perhaps it's a naive attempt, but I've made the loader
a data access layer containing the database logic, which the service layer accesses and doesn't overlap with. Trouble I'm having is @Inject(CONTEXT) private context
seems to throw an error when implemented like in the docs: https://docs.nestjs.com/fundamentals/injection-scopes. Really need to initialise the dataloaders with the current user within the request to work out access rights, like in the dataloader docs: https://github.com/graphql/dataloader#creating-a-new-dataloader-per-request.
Anyone else having this issue?
EDIT: Got it working getting the current user through the request, although I couldn't get the param decorator to work at the moment (like with the resolvers):
import { Inject, Injectable, Scope } from '@nestjs/common'
import { CONTEXT } from '@nestjs/graphql'
import { InjectRepository } from '@nestjs/typeorm'
import DataLoader from 'dataloader'
import { Repository } from 'typeorm'
import { Project } from './project.entity'
interface RequestUser {
req: {
user: { id: string }
}
}
@Injectable({ scope: Scope.REQUEST })
export class ProjectLoader {
constructor(
@Inject(CONTEXT) private context: RequestUser,
@InjectRepository(Project)
private readonly projectRepository: Repository<Project>
) {}
readonly findOneById = new DataLoader<string, Project | undefined>(
async (ids) => {
const user = this.context.req.user // Would ideally use decorator for this instead (i.e. from createParamDecorator)
const projects = await this.projectRepository
.createQueryBuilder('project')
.where('project.id IN (:...ids)', { ids })
.andWhere('project.owner = :userId', { userId: user.id })
.getMany()
return ids.map((id) => projects.find((n) => n.id === id))
}
)
}
I came up with the following that seems to work nicely.
fixture.module.ts
@Module({ imports: [TypeOrmModule.forFeature([FixtureRepository])], providers: [FixtureService, FixtureLoaders, FixtureResolvers], }) export class FixtureModule {}
fixture.loaders.ts
@Injectable({ scope: Scope.REQUEST }) export class FixtureLoaders { constructor(private readonly fixtureService: FixtureService) {} public readonly findByCode = new DataLoader<number, Fixture>(async codes => { try { const fixtures = await this.fixtureService.findByCodes(codes) return codes.map(code => fixtures.find(fixture => fixture.code === code)) } catch (error) { throw error } }) }
fixture.resolvers.ts
@Injectable() export class FixtureResolvers { constructor(private readonly fixtureLoaders: FixtureLoaders) {} @Query('getFixtureByCode') async getFixtureByCode( @Args('code') code: number, ): Promise<FixtureRO | undefined> { try { const fixture = await this.fixtureLoaders.findByCode.load(code) if (fixture !== undefined) { return fixture.toResponseObject() } else { return undefined } } catch (error) { throw error } } }
This is by far the most elegant solution.
I too have the problem when implementing the soltion marked by @kamilmysliwiec as Tutorial
that I see multiple queries executed.
The only difference between the provided solution and my solution is that I am loading a related entity based on a key of the main value, I do however map them accordingly so don't see how that could be the problem.
Is there something specific to take into account to make sure they keys are batched before sent to the service function?
My solution does not provide multiple queries in one request to from the frontend to the backend either by the way, it asks for a list of entities and then loads sublists of entities which should be batched by the dataloader but are not. I am using @ResolveField for that field.
Any ideas?
@Artimunor I was having a problem with @incompletude solution because not every field resolver in my application was using dataloaders, thus desyncing the execution and preventing the dataloaders to batch. When I applied the dataloaders to every field resolver of my graph it worked correcty batching every query on the same depth of the graph.
I came up with the following that seems to work nicely.
fixture.module.ts
@Module({ imports: [TypeOrmModule.forFeature([FixtureRepository])], providers: [FixtureService, FixtureLoaders, FixtureResolvers], }) export class FixtureModule {}
fixture.loaders.ts
@Injectable({ scope: Scope.REQUEST }) export class FixtureLoaders { constructor(private readonly fixtureService: FixtureService) {} public readonly findByCode = new DataLoader<number, Fixture>(async codes => { try { const fixtures = await this.fixtureService.findByCodes(codes) return codes.map(code => fixtures.find(fixture => fixture.code === code)) } catch (error) { throw error } }) }
fixture.resolvers.ts
@Injectable() export class FixtureResolvers { constructor(private readonly fixtureLoaders: FixtureLoaders) {} @Query('getFixtureByCode') async getFixtureByCode( @Args('code') code: number, ): Promise<FixtureRO | undefined> { try { const fixture = await this.fixtureLoaders.findByCode.load(code) if (fixture !== undefined) { return fixture.toResponseObject() } else { return undefined } } catch (error) { throw error } } }
This is by far the most elegant solution.
Thanks for this man! I've been looking on this solution, works perfectly on @ResolveField.
I am initially using nestjs-dataloader
but encountering problem if there are 2 or more interceptors being applied, it's like you can only use 1 dataloader on your whole app. Thanks for this solution.
I came up with the following that seems to work nicely.
fixture.module.ts
@Module({ imports: [TypeOrmModule.forFeature([FixtureRepository])], providers: [FixtureService, FixtureLoaders, FixtureResolvers], }) export class FixtureModule {}
fixture.loaders.ts
@Injectable({ scope: Scope.REQUEST }) export class FixtureLoaders { constructor(private readonly fixtureService: FixtureService) {} public readonly findByCode = new DataLoader<number, Fixture>(async codes => { try { const fixtures = await this.fixtureService.findByCodes(codes) return codes.map(code => fixtures.find(fixture => fixture.code === code)) } catch (error) { throw error } }) }
fixture.resolvers.ts
@Injectable() export class FixtureResolvers { constructor(private readonly fixtureLoaders: FixtureLoaders) {} @Query('getFixtureByCode') async getFixtureByCode( @Args('code') code: number, ): Promise<FixtureRO | undefined> { try { const fixture = await this.fixtureLoaders.findByCode.load(code) if (fixture !== undefined) { return fixture.toResponseObject() } else { return undefined } } catch (error) { throw error } } }
For some reason, the @nestjs/event-emitter
is not working if it detects any injectables that has Scope.REQUEST
on it (Even if that service has nothing to do with the module thats using the event emitter). That's why i temporarily remove the Scope.REQUEST
on the dataloader injectables to make my event emitter work
Hey guys. I also tried the solution provided by @jeppe-smith. This works fine on ResolveFields and such, but only on Queries. Could be my general misunderstanding of the whole graphQL part of NestJS but I stumbled upon it while implementing this solution. I had as well problems to implement the callBack async like described here. Just to mention that for others: The dataloaders on the ResolveFields are not getting used in case of a mutation or subscriptions.
I came up with the following that seems to work nicely.
fixture.module.ts
@Module({ imports: [TypeOrmModule.forFeature([FixtureRepository])], providers: [FixtureService, FixtureLoaders, FixtureResolvers], }) export class FixtureModule {}
fixture.loaders.ts
@Injectable({ scope: Scope.REQUEST }) export class FixtureLoaders { constructor(private readonly fixtureService: FixtureService) {} public readonly findByCode = new DataLoader<number, Fixture>(async codes => { try { const fixtures = await this.fixtureService.findByCodes(codes) return codes.map(code => fixtures.find(fixture => fixture.code === code)) } catch (error) { throw error } }) }
fixture.resolvers.ts
@Injectable() export class FixtureResolvers { constructor(private readonly fixtureLoaders: FixtureLoaders) {} @Query('getFixtureByCode') async getFixtureByCode( @Args('code') code: number, ): Promise<FixtureRO | undefined> { try { const fixture = await this.fixtureLoaders.findByCode.load(code) if (fixture !== undefined) { return fixture.toResponseObject() } else { return undefined } } catch (error) { throw error } } }
This solution first seems elegant, but it quite a bad design because the dataloader is a request scope provider, so any other providers that depend on it will also become request scope. It means that the resolver provider will be recreated every incoming request. Refer to https://docs.nestjs.com/fundamentals/injection-scopes#scope-hierarchy
This solution first seems elegant, but it quite a bad design because the dataloader is a request scope provider, so any other providers that depend on it will also become request scope.
Just FYI: there's nothing wrong with that. This is precisely why the "request-scoped" providers feature was added to the framework.
Haven't seen dataloaders for typeorm relationships like https://github.com/slaypni/type-graphql-dataloader#with-typeorm
Has anyone done anything like that? I'm assuming the above doesn't work with nestjs/graphql since it doesn't use type-graphql.
I strongly believe this should be properly addressed in docs. At least some guidance regarding how to use DataLoader
with NestJS(similar to Authz with Casl).
For anyone who is struggling with this, I wrote an article about How I integrated Dataloader with NestJS
Hope this helps anyone else who is struggling with the same problem 😃
Are there any relevant examples?
https://github.com/facebook/dataloader