Why can't I use my nestjs custom decorator in a constructor? - node.js

I want to create a custom decorator to return tenant information. I have the following code:
export type TenantInfo = {
token: string
id: string
}
export const TenantInfo = createParamDecorator(
(data: unknown, context: ExecutionContext): TenantInfo => {
// todo use context to get from auth header
return { id: '1', token: 'todo' }
},
);
Now i register it in the tenant service:
#Injectable({ scope: Scope.REQUEST })
export class TenantService {
private readonly _tenantInfo: TenantInfo;
constructor(
#TenantId() tenantInfo: TenantInfo,
) {
this._tenantInfo = tenantInfo;
logger.log(`Received GRPC message from tenant id ${tenantInfo.id}`);
}
}
And when I run my code it says:
Error: Nest can't resolve dependencies of the TenantService (?).
Please make sure that the argument Object at index [0] is available in
the TenantModule context.
Potential solutions:
If Object is a provider, is it part of the current TenantModule?
If Object is exported from a separate #Module, is that module imported within TenantModule? #Module({
imports: [ /* the Module containing Object */ ]
I'm not sure how to get around this. It's saying register it in the module but I get errors if I try putting TenantInfo in the providers array...
Type '(...dataOrPipes: unknown[]) => ParameterDecorator' is not
assignable to type 'Provider'
How do you do this? All I want is to get some details from the request context.

createParamDecorator only creates decorators that work for route handlers (Controllers, Resolvers, or Gateways). For something that can be injected at the constructor level of a service you can either inject the request, via #Inject(REQUEST), or you can create a custom request scoped provider and custom decorator like so:
export const InjectTenantId = () => Inject('TENANTID');
export const tenantIdProvider = {
provide: 'TENANTID',
inject: [REQUEST],
scope: Scope.REQUEST,
useFactory: (request) => getTenantIdFromRequest(request)
}
And now in your constructor you can do #InjectTenantId() private readonly tenantInfo: TenantInfo.

Related

How to export APP_GUARD from nestjs module?

I have module which implement JWT passport
const appGuard = {
provide: APP_GUARD,
useClass: JwtAuthGuard,
};
#Module({})
export class JwtAuthModule {
static forRoot(options: { jwks: string, excludePath: string[] }): DynamicModule {
const exportableProviders = [
{ provide: JWKS_URI, useValue: options.jwks },
{ provide: EXCLUDE_FROM_AUTH, useValue: options.excludePath },
appGuard,
];
return {
module: JwtAuthModule,
providers: [
...exportableProviders,
JwtStrategy,
JwtAuthGuard
],
exports: exportableProviders,
};
}
}
But I am getting an error:
Error: Nest cannot export a provider/module that is not a part of the
currently processed module (JwtAuthModule). Please verify whether the
exported APP_GUARD is available in this particular context.
Possible Solutions:
Is APP_GUARD part of the relevant providers/imports within JwtAuthModule?
What is interesting if I debug i see that APP_GUARD has name in providers like: APP_GUARD (UUID: 63cf5b8e-815e-4034-a4a8-c6196d98b612)
Simple work around is to register APP_GUARD in providers at each root module and import module, but i would love to be able just to import this module once if possible...
APP_GUARD, along with the other APP_* providers, are special provider tokens that Nest allows to be bound multiple times. As soon as it is used it is bound as a global enhancer of whatever type relating back to the token (pipe, guard, interceptor, filter). As it's bound, there's no need to export the enhancer as well, there's nothing else that can make use of it directly, it's bound globally so it's already fine as is. Just remove the APP_GUARD provider you have from the exports and you should be good to go

How to get method and its metadata from NestJS provider?

I'm making a NestJS wrapper for Typegoose because the existing one is complete deprecated and has one critical drawback that I want to fix in my implementation.
Problem: there is #EventTrackerFor(schema: AnyClass) that takes Typegoose class. It's implemented like this:
export const EventTrackerFor = (schema: AnyClass) =>
applyDecorators(Injectable, SetMetadata('tracker-for', schema.name));
Also, there are #Pre(eventName: PreEvents) and Post(eventName: PostEvents) decorators:
export const Post = (eventName: PreEvents) => SetMetadata('post', eventName);
export const Pre = (eventName: PostEvents) => SetMetadata('pre', eventName);
And as a result, library user will do it like that:
#EventTrackerFor(User)
class UserEventTracker {
constructor(private readonly anyService: AnyService) {}
#Pre(PreEvents.SAVE)
#Post(PostEvents.SAVE)
logOnAndAfterCreate() {
console.log('user created')
}
}
// ------------------------ Any module
#Module({
imports: [MyModule.forFeature([ {schema: User} ])],
providers: [UserEventTracker]
})
class AnyModule {}
I need to get value from #EventTrackerFor somehow and methods of the provider, which are decorated with #Pre() and #Post decorators, including values that passed inside them.
I was looking for a clue in different packages like #nestjs/bull, which has similar logics inside, but they have so much code, so I could not understand how do they do it.
Project repository: https://github.com/GrapeoffJS/kindagoose

Proper way to manually instantiate Nest.js providers

I think I might be misunderstanding Nest.js's IoC container, or maybe DI as a whole.
I have a class, JSONDatabase, that I want to instantiate myself based on some config value (can either be JSON or SQL).
My DatabaseService provider:
constructor(common: CommonService, logger: LoggerService) {
// eslint-disable-next-line prettier/prettier
const databaseType: DatabaseType = common.serverConfig.dbType as DatabaseType;
if (databaseType === DatabaseType.JSON) {
this.loadDatabase<JSONDatabase>(new JSONDatabase());
} else if (databaseType === DatabaseType.SQL) {
this.loadDatabase<SQLDatabase>(new SQLDatabase());
} else {
logger.error('Unknown database type.');
}
}
My JSONDatabase class:
export class JSONDatabase implements IDatabase {
dbType = DatabaseType.JSON;
constructor(logger: LoggerService, io: IOService) {
logger.log(`Doing something...`)
}
}
However, the problem with this is that if I want my JSONDatabase to take advantage of injection, ie. it requires both IOService and LoggerService, I need to add the parameters from the DatabaseService constructor rather than inject them through Nest's IoC containers.
Expected 2 arguments, but got 0 [ts(2554)]
json.database.ts(7, 15): An argument for 'logger' was not provided.
Is this the proper way to do this? I feel like manually passing these references through is incorrect, and I should use Nest's custom providers, however, I don't really understand the Nest docs on this subject. I essentially want to be able to new JSONDatabase() without having to pass in references into the constructor and have the Nest.js IoC container inject the existing singletons already (runtime dependency injection?).
I might be completely off base with my thinking here, but I haven't used Nest all that much, so I'm mostly working off of instinct. Any help is appreciated.
The issue you have right now is because you are instantiating JSONDatabase manually when you call new JSONDatabase() not leveraging the DI provided by NestJS. Since the constructor expects 2 arguments (LoggerService, and IOService) and you are providing none, it fails with the message
Expected 2 arguments, but got 0 [ts(2554)]
I think depending on your use case you can try a couple of different options
If you fetch your configuration on startup and set the database once in the application lifetime you can use use a Custom provider with the useFactory syntax.
const providers = [
{
provide: DatabaseService,
useFactory: (logger: LoggerService, io: IOService, config: YourConfigService): IDatabase => {
if (config.databaseType === DatabaseType.JSON) {
return new JSONDatabase(logger, io);
} else if (databaseType === DatabaseType.SQL) {
return new SQLDatabase(logger, io);
} else {
logger.error('Unknown database type.');
}
},
inject: [LoggerService, IOService, YourConfigService]
},
];
#Module({
providers,
exports: providers
})
export class YourModule {}
If you have LoggerService, IOService and YourConfigurationService annotated with #Injectable() NestJS will inject them in the useFactory context. There you can check the databaseType and manually instantiate the correct IDatabase implementation. The drawback with this approach is that you can't easily change the database in runtime. (This might work just fine for your use case)
You can use strategy/factory pattern to get the correct implementation based on a type. Let say you have a method that saves to different databases based on an specific parameter.
#Injectable()
export class SomeService {
constructor(private readonly databaseFactory: DatabaseFactory){}
method(objectToSave: Object, type: DatabaseType) {
databaseFactory.getService(type).save(objectToSave);
}
}
#Injectable()
export class DatabaseFactory {
constructor(private readonly moduleRef: ModuleRef) {}
getService(type: DatabaseType): IDatabase {
this.moduleRef.get(`${type}Database`);
}
}
The idea of the code above is, based on the database type, get the correct singleton from NestJS scope. This way it's easy to add a new database if you want - just add a new type and it's implementation. (and your code can handle multiple databases at the same time!)
I also believe you can simply pass the already injected LoggerService and IOService to the DatabasesService you create manually (You would need to add IOService as a dependency of DatabaseServce
#Injectable()
export class DatabaseService {
constructor(common: CommonService, logger: LoggerService, ioService: IOService) {
// eslint-disable-next-line prettier/prettier
const databaseType: DatabaseType = common.serverConfig.dbType as DatabaseType;
if (databaseType === DatabaseType.JSON) {
this.loadDatabase<JSONDatabase>(new JSONDatabase(logger, ioService));
} else if (databaseType === DatabaseType.SQL) {
this.loadDatabase<SQLDatabase>(new SQLDatabase(logger, ioService));
} else {
logger.error('Unknown database type.');
}
}
}

How do you use a fallback exception filter in NestJs

I'm new to NestJs and I created a fallback exception filter, and now I would like to know how to use it. In other words, how do I import it in my application?
Here's my fallback exception filter:
#Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
public catch(exception: HttpException, host: ArgumentsHost): any {
/* Some code here */
return response.status(statusCode).json({
status: statusCode,
datetime: new Date(),
createdBy: "HttpExceptionFilter",
errorMessage: exception.message,
})
}
}
You'd need to bind the filter globally to be the fallback. You can do this one of two ways
With a custom provider in any module. Add this to the module's providers array
{
provide: APP_FILTER,
useClass: HttpExceptionFilter
}
This will still take effect in e2e tests, as it's part of the module definition
By using useGlobalFilters in your bootstrap method like so
app.useGlobalFilters(new HttpExceptionFilter());
This will not take effect in your e2e tests, so you'll need to bind it in those too, if you want the same functionality.
Just add this in your main.ts and it should work fine:
app.useGlobalFilters(new FallbackExceptionFilter();

Inject service into guard in Nest.JS

I have KeysModule, which can be used to add or remove API keys. I need these keys to protect some routes from unauthorized access.
To protect these routes I have created ApiGuard:
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
#Injectable()
export class ApiGuard implements CanActivate {
async canActivate(
context: ExecutionContext,
): Promise<boolean> {
const request = context.switchToHttp().getRequest();
return request.headers.api_key;
}
}
And then I use it in route:
#Get('/protected')
#UseGuards(ApiGuard)
async protected(#Headers() headers: Api) {
const key = await this.ks.findKey({ key: headers.api_key });
if (!key || !key.active) return 'Invalid Key';
return 'Your API key works';
}
Where ks is KeyService used to check if key is correct or not.
This solution works, but is stupid. I have to copy and paste some lines of code everywhere I want to use this guard (I mean lines in route).
I have tried to to move all logic to ApiGuard, but there I have got error, that KeyService cannot be injected to ApiGuard class. To explain, I have KeyService in providers in KeysModule, but ApiGuard is globally used.
Do you have any idea how to do it?
As of NestJS v8 it seems injecting the service as answered by zsoca in the accepted answer doesn't work anymore.
The working solution for NestJS 8 is by providing a class reference instead of a string:
constructor(#Inject(KeyService) private keyService: KeyService) {}
To inject service in guard.You can create a global module.
// ApiModule
import {Module,Global} from '#nestjs/common';
import {KeyService} from '../';
#Global()
#Module({
providers: [ KeyService ],
exports: [KeyService]
})
export class ApiModule {}
Then inject service into guard like this
// guard
export class ApiGuard implements CanActivate {
constructor(#Inject('KeyService') private readonly KeyService) {}
}
async canActivate(context: ExecutionContext) {
// your code
throw new ForbiddenException();
}
Now the problem can be solved.But I have another problem.I want to inject something into service but got this error:
Nest can't resolve dependencies of the AuthGuard (?, +). Please make sure that the argument at index [0] is available in the current context.
And here is my solution:
To Inject other dependency in KeyService,like nestjs docs say.
global guards registered from outside of any module (with useGlobalGuards() as in the example above) cannot inject dependencies since this is done outside the context of any module.
This is their sample:
// app.module.js
import { Module } from '#nestjs/common';
import { APP_GUARD } from '#nestjs/core';
#Module({
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class ApplicationModule {}
It worked.Now I can use guard global without dependency error.
Maybe it's too late, but I ran the same issue and find a solution. Maybe there is a better one, but it's working properly for me:
Define KeysModule as a global module, you can check how to do it in nestjs docs: https://docs.nestjs.com/modules
After You can do this:
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
#Injectable()
export class ApiGuard implements CanActivate {
constructor(
#Inject('KeyService')
private readonly ks
) {}
const key = await this.ks.findKey();
"YOUR_CODE_HERE..."
}
Hope it's help to you or somebody who will stuck with this in the future.
You can inject a service in a guard like in any object annotated with Injectable.
If your ApiGuard need KeyService you have two choices:
Add ApiGuard in a module which import KeysModule. Then import the created module to use ApiGuard globally
Add ApiGuard in KeysModule and export it.
Services used in the guard must be exported in its module. Providing service is not enough!
Let me clarify some points here! Nestjs Guard layer was designed to let you interpose processing logic at exactly the right point in the request/response cycle and this was treated as a normal instance in Nestjs DI Container.
So what does it means when I said this was treated as a normal instance in Nestjs DI Container -> So whenever it has dependencies so that wherever it is used you need to also inject it own dependencies to where you are using. Let me give you some example:
You first have a module corresponding to handle tokens as you mentioned ^^.
#Module({
imports: [CacheModule.register()],
providers: [
TokenService,
],
exports: [TokenService],
})
export class TokenModule {}
And a Guard use it to do something ...
class YourCustomAuthGuard implements CanActive {
constructor(private readonly tokenService: TokenService) {}
canActive(executionCtx: ExecutionContext) {
this.tokenService.getSth() // ...
}
}
And you have some modules like Post or Category and there are some routing needs to verify your token existed by using guard.
#Controller('posts')
class PostController {
#UseGuards(YourCustomAuthGuard)
#Get()
getSelfPosts() {}
}
#Controller('categories')
class CategoryController {
#UseGuards(YourCustomAuthGuard)
#Get()
getPermittedCategories() {}
}
The thing here you want to use YourCustomAuthGuard as a special dependency in Post and Category module which we don't need to declare in Module as Nestjs do understand and treat Guard as a special dependency for handling execution context. So the case here is your Guard depended on TokenModule then wherever you use your Guard you need to inject TokenModule to follow with YourCustomGuard. Below is the example of PostModule and CategoryModule:
#Module({
imports: [TokenModule, TypeOrmModule.feature([Post])], // <-- Take a look at TokeModule that I have been declared
controllers: [PostController],
providers: [PostService],
exports: [PostService],
})
class PostModule {}
#Module({
imports: [TokenModule, TypeOrmModule.feature([Category], // <-- Take a look at TokeModule that I have been declared
controllers: [CategoryController],
providers: [CategoryService],
exports: [CategoryService],
})
class CategoryModule {}
So that this way can resolve your problem as your guard is used independently and does not belong to the global scope.
So what if you need to use it in your global scope? In my opinion there are two ways:
Firstly, your dependencies also belong to a global module. Let me give out some examples:
// This is the root module
#Module({
imports: [TokenModule], // <- TokenModule you declared as a global module
providers: [
{
provide: APP_GUARD,
useClass: YourCustomAuthGuard,
},
]
})
class AppModule {}
Second way is to construct directly when declaring a global guard (This way is not recommended)
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
{
logger: true,
});
const tokenService = app.get(TokenService);
app.useGlobalGuards(new YourCustomAuthGuard(tokenService));
After all, we may have some ways to resolve dependencies in nestjs guard but there is one thing that belongs to passport and nestjs design I want to present here because it may help you to have a better view to loosely coupling between nest guard and validation strategy separately and we no need to inject dependencies anywhere else. Before continuing reading the below topic, I recommend viewers that read and try with nestjs authentication before!
There are maybe some devs may used and read about nestjs authentication before and maybe also curious about how PassportStrategy and PassportGuard interact with each other and also we can easily inject things into PassportStrategy, right?
I won't go deep down into passport and nestjs implementation but I will explain about the concept then you guys can have an overview about this one.
Firstly, keep in my that guard layer still be triggered as normal if we use it in protecting any route and then we go next to the triggering strategy! But wait, we don't see any relationship between our Guard and Strategy so how can it work? I will give you an example of how I implemented a module using passport!
#Module({
imports: [
UserModule,
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET'),
}),
inject: [ConfigService],
}),
],
controllers: [AuthController],
providers: [
JwtStrategy, // <- We provide JwtStrategy as a provider here
{
provide: AuthServiceToken,
useClass: AuthServiceImpl,
},
],
})
export class AuthModule {}
Be aware that JwtStrategy extends from PassportStrategy and our guard also extends from AuthGuard of nestjs/passport.
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
const secret = configService.get<string>('JWT_SECRET');
assert(secret, 'JWT_SECRET is missing');
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: secret,
});
}
validate(payload: JwtPayload): JwtPayload {
return payload;
}
}
Actually, behind scenes, passport is a separate instance out of the box of nestjs DI Container so the author of nestjs kamil created #nestjs/passport as an adapter to this library.
Firstly, let's focus on our JwtAuthGuard, we extend from AuthGuard('jwt') which is a guard factory actually and it use 'jwt' to identify strategy to be used next so that after finishing guard validation, it can make a round trip to strategy in next section under the hood.
And next, pay attention on JwtStrategy, this is where the cool part happen, this one would be initialized as a normal provider in the nestjs container so that everything can easily inject to this one and after nest injected dependencies into this one, nestjs will pass this instance reference to Passport so that can be auto triggering under the hood after AuthGuard finish their job at runtime! Read more about nestjs passport strategy and focus on passportInstance.use and spend time you would have a nice idea to use stragy out of the box with Guard in nestjs. Good luck ^^
Another approach without making the service global (so no #Global, and no #Inject) worked for me (on Nest 9)
File Structure
├── app
│ └── app.module.ts # main application module
├── status
│ ├── status.module.ts
│ └── status.service.ts # status service (which is to be injected)
├── hodor
│ ├── hodor.decorators.ts # avoid using #UseGuards(HodorGuard) => use #NotDangerousNow() instead
│ └── hodor.guard.ts # the guard using StatusService
└── door
├── door.module.ts
└── door.controller.ts # the controller guarded by #NoDanger()
AppModule
import { Module } from '#nestjs/common'
import { APP_GUARD } from '#nestjs/core'
import { DoorModule } from '../door/door.module'
import { HodorGuard } from '../hodor/hodor.guard'
import { StatusModule } from '../status/status.module'
#Module({
imports: [
HodorModule,
DoorModule,
StatusModule
],
providers: [
{ provide: APP_GUARD, useClass: HodorGuard } // makes the guard used everywhere
]
})
export class AppModule {}
StatusModule
import { Module } from '#nestjs/common'
import { StatusService } from './status.service'
#Module({
providers: [StatusService],
exports: [StatusService]
})
export class StatusModule {}
StatusService
import { Injectable } from '#nestjs/common'
#Injectable()
export class StatusService {
/**
* Returns whether Hodor thinks it's dangerous at the given time.
* Just async because in general there is a db call there.
*/
async isDangerousAt(date: Date): Promise<boolean> {
return date.getHours() > 22
}
}
hodor.decorators.ts
import { SetMetadata } from '#nestjs/common'
export const DANGER_KEY = 'danger'
// 403 if Hodor thinks it's dangerous at the given date/time
// usage: #NotDangerous(date) => that's right I see no possible usage for that one, it's for the example
export const NotDangerous = (date: Date) => SetMetadata(DANGER_KEY, { date })
// 403 if Hodor thinks it's dangerous right now
// usage: #NotDangerousNow()
export const NotDangerousNow = () => SetMetadata(DANGER_KEY, { date: new Date() })
hodor.guard.ts
import { Injectable, CanActivate, ForbiddenException } from '#nestjs/common'
import { Reflector } from '#nestjs/core'
import { DANGER_KEY } from './hodor.decorator'
import { StatusService } from '../status/status.service'
type HodorMetadata = {
status: PlatformHodor
expected: boolean
}
#Injectable()
export class HodorGuard implements CanActivate {
constructor(
private reflector: Reflector,
private readonly statusService: StatusService // Do not use #Inject (or nest won't be able to inject it)
) {}
/**
* Rely on status service to check if Hodor thinks it is dangerous at the given date/time.
* #throws ForbiddenException if Hodor thinks it's dangerous at the given date/time => 403
*/
async canActivate(context: any): Promise<boolean> {
// METADATA DANGER_KEY is the magic link between NotDangerous decorator and the guard
const metadata = this.reflector.getAllAndOverride<HodorMetadata>(
DANGER_KEY,
[context.getHandler(), context.getClass()]
)
// because we inject the guard in the whole app
// => it must let pass in routes with no decorator
if (!metadata) return true
// 403 if dangerous
const isDangerous = await this.statusService.isDangerousAt(metadata.date)
if (isDangerous) {
throw new ForbiddenException(`Hodor thinks it's dangerous on ${metadata.date}`)
}
// let pass otherwise
return true
}
}
DoorModule
import { Module } from '#nestjs/common'
import { DoorController } from './door.controller'
#Module({
controllers: [DoorController],
providers: [DoorService],
})
export class DoorModule {}
DoorService
import { Controller, HttpCode, Post } from '#nestjs/common'
import { NotDangerousNow } from '../hodor/hodor.decorator'
#Controller('door')
export class DoorController {
#NotDangerousNow()
#Post()
open(): Promise<string> {
return 'please, come in'
}
}

Resources