Papooch / nestjs-cls

A continuation-local storage (async context) module compatible with NestJS's dependency injection.
https://papooch.github.io/nestjs-cls/
MIT License
390 stars 23 forks source link

bootstrap issue - Cannot read properties of undefined (reading 'getStore') #79

Closed mkgn closed 11 months ago

mkgn commented 11 months ago

As per the documentation here I chose to setup using the app.Use method and it looks like below

const app = await NestFactory.create<NestFastifyApplication>(AppModule,new FastifyAdapter());

//https://papooch.github.io/nestjs-cls/
const options: ClsMiddlewareOptions = {
  mount: true,
  setup: (context, req: Request, res: Response) => {
    if (!req.url.startsWith('/api'))
      context.set(SystemConstants.CurrentContext, new CurrentUserContext()); 
  },      
};
app.use(new ClsMiddleware(options).use);

Then I have registered the ClsService in my AppModule like below

@Module({
  imports: [
    AppSettingsModule    
  ]
  ,controllers: [AppController]
  ,providers: [ClsService]
})
export class AppModule implements NestModule{
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(SwaggerAuthMiddleware).forRoutes('api');
  }
}

After that I inject the ClsService in one of my guards and use it like below

  @Injectable()
    export class AuthGuard implements CanActivate {
      constructor(private readonly contextService:ClsService, private readonly authService:AuthService, private readonly reflector: Reflector) {}

      async canActivate(context: ExecutionContext): Promise<boolean> {

    const test = this.contextService.get(SystemConstants.CurrentContext)
    return true;
}
    }

and at this line const test = this.contextService.get(SystemConstants.CurrentContext) it gives this error Cannot read properties of undefined (reading 'getStore')

I can't figure out any missing configurations I may have done. Btw. I am using FastifyAdapter. So not sure whether it has something to do with it.

Papooch commented 11 months ago

There's multiple things going on in your setup that are wrong, let me go through them one by one:

1) If you're mounting the middleware manually, you need to set mount to false. However, in your setup it does not make a difference because you're not passing the options through ClsModule.forRoot

2) I do not understand why you're trying to mount the ClsMiddleware globally and then filter the route inside the setup. If you don't absolutely need to do that because of the order of other middlewares used, I would recommend mounting it in the AppModule instead only to the routes where you need it.

3) Even if you register the middleware manually, you need to register the ClsModule.forRoot and do not put ClsService in providers. It's provided by ClsModule.

The error you're seeing is caused precisely by this^. The ClsService has one constructor parameter, which is the AsyncLocalStorage instace which you don't provide and Nest can't inject it - it's an implementation detail though, you shouldn't ever be putting 3rd party classes into providers because there's usually more that needs to be set up by the module.

It's like if you were to put CacheService from @nestjs/cache-manager directly into the providers array. It wouldn't work because it needs external setup like the data store configuration, which is done by the CacheModule.register method, which also in turn provides the CacheService for injection. The providers array should only be used for your application's providers - 3rd party ones are usually brought in via a module.


That said, the correct code (in my opinion) should look like this:

@Module({
  imports: [
    AppSettingsModule,
    ClsModule.forRoot({
      global: true // <- make ClsService available for injection globally
                   // alternatively, import `ClsModule` explicitly in modules where it is needed
      middleware: {
        mount: false // <- do not mount automatically (default, but better to set explicitly)
        setup: (context, req: Request, res: Response) => {
          context.set(SystemConstants.CurrentContext, new CurrentUserContext()); 
        }
      }
    }) 
  ],
  controllers: [AppController],
  providers: []
})
export class AppModule implements NestModule{
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(
      ClsMiddleware, // <- mount middleware manually here, settings from module will apply
      SwaggerAuthMiddleware
    ).forRoutes('api');
  }
}

If, for some reason, you do need to mount the middleware in main.ts, you still have to import the ClsModule.forRoot(Async) to provide the ClsService.

mkgn commented 11 months ago

great! got it to work and thank you!