I’m building a NestJs app, that for each single request, needs to setup a third party client instance, which depends on a user-specific access token, which gets resolved through the request headers. A request may call several service methods, and I would like to avoid having to pass the client to each method individually.
So I’m trying to implement a request scoped service that I can resolve in each service method to get access to that third party client instance.
I have a fairly straightforward setup in NestJs.
AppModule:
@Module({
imports: [
// config
ConfigModule.forRoot(),
// db
MikroOrmModule.forRoot(config),
// gql
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
imports: [],
inject: [],
useFactory: () => ({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
context: async ({ req, res }: { req: Request, res: Response }): Promise<GraphQLContext> => {
return {
req,
res,
auth: { userId: 'xxx' // retrieved from request }
}
},
resolvers: {
UUID: CustomUuidScalar,
},
}),
}),
// modules
UserModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
UserModule:
@Module({
imports: [MikroOrmModule.forFeature([User]), ThirdPartyModule],
providers: [UserService, UserResolver, ThirdPartyClientService],
exports: [UserService]
})
export class UserModule {}
User resolver
@Resolver(() => User)
export class UserResolver {
constructor(
private userService: UserService,
) {}
@Query(() => User)
me(@Context() context: GraphQLContext) {
return this.userService.getUser(context.auth.userId)
}
}
UserService (using moduleRef)
@Injectable()
export class UserService {
constructor(
private readonly moduleRef: ModuleRef,
) {}
async getUser(userId: string, req: any): Promise<User> {
console.log('getUser')
// Dynamically resolve the request-scoped ThirdPartyClientService
// Cannot directly inject because that will break the /graphql route so use moduleRef
const clientService = await this.moduleRef.resolve(ThirdPartyClientService);
return clientService.getClient().findUser(userId)
}
}
ThirdPartyModule
@Module({
imports: [],
providers: [ThirdPartyClientService],
exports: [ThirdPartyClientService],
})
export class ThirdPartyModule {}
ThirdPartyClientService
@Injectable({ scope: Scope.REQUEST })
export class ThirdPartyClientService {
private client: ThirdPartyClient;
constructor(@Inject(CONTEXT) private context: GraphQLContext) {
console.log(this.context); // this.context is undefined here!
this.client = new ThirdPartyClientInstance(this.context.auth.userId)
}
getClient() {
return this.client
}
}
Why is this.context
in ThirdPartyClientService undefined
?
Here is a minimal repo that has the same issue.