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
Related
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.');
}
}
}
So, I came across a situation where I need to get the metadata of the Current Module, and its controllers.
ClosedModule.ts
// all imports to AModule, BModule
#Module({
imports: [
DiscoveryModule, // given by nest/core
AModule, // it has a controller AController whose base path is A/ and has 1 function for #Get a1
BModule, // it has a controller BController whose base path is B/ and has 1 function for #Get b1
],
})
export class ClosedModule implements OnModuleInit { // ---> (X)
constructor(private readonly discovery: DiscoveryService) {}
public async onModuleInit() {
const controllers = await this.discovery.getControllers({});
controllers.map(c => console.log('---->', c.name));
}
}
In above code:
DiscoveryService, DiscoveryModule imported from '#nestjs/core';
I tried getting the information using the above code. But, there are some issues:
I am getting all the controllers in the array, rather I just need
the controllers refs for the ClosedModule class i.e current class, (X).
How can I get the base path of all the controllers under a module which IMPORTED.
2.1 Other modules
2.2 Other Controllers
Thanks in advance
Happy Coding :)
You have access to a container in your module's constructor, which has all the configurations given to the imported modules.
Here
#Module({})
export class YourModules {
constructor(readonly modulesContainer: ModulesContainer) {
const modules = [...modulesContainer.values()];
for (const nestModule of modules) {
const modulePath: string = Reflect.getMetadata(MODULE_PATH, nestModule.metatype);
}
}
// ...
}
You can find a more detailed example in the nestjsx/nest-router library.
I'm currently working through the database integration docs for NestJS using TypeOrm. In these docs there are examples that show how to inject a custom database repository using the app.module from NestJS. All of these examples inject classes using the actual type of the custom repository.
#Injectable()
export class AuthorService {
constructor(private authorRepository: AuthorRepository) {}
}
This code is injected via the app.modules by providing a import like such:
#Module({
imports: [TypeOrmModule.forFeature([AuthorRepository])],
controller: [AuthorController],
providers: [AuthorService],
})
export class AuthorModule {}
This works well if you are fine with programming against an implementation, but I prefer to use an interface in my classes. I've already found the solution to injecting classes via an interface with NestJS in a previous question, but when I try to inject my custom repository like that, it doesn't seem to instanciate correctly and becomes undefined.
(node:16658) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'save' of undefined
Because of this, I assume you can only inject customRepositories via the forFeature() call in the app.module, but that won't allow me to use interfaces for injection, as far as I know. Is there any other way I can inject a custom TypeOrm repository without having the replace all my interfaces for the implementation of my custom repository? Thanks in advance!
Edit
Here is my current code, I managed to get it to inject, but this still forces me to use the implementation instead of the interface each time I call the constructor. This is mainly an issue when testing due to mocking.
#CommandHandler(FooCommand)
export class FooHandler
implements ICommandHandler<FooCommand> {
private fooRepository: IFooRepository; // Using Interface as a private property.
private barEventBus: IEventBus;
constructor(fooRepository: FooRepository,
barEventBus: EventBus) { // Forced to use implementation in constructor for injection.
this.fooRepository = fooRepository;
this.barEventBus = barEventBus;
}
#EntityRepository(FooEntity)
export class FooRepository extends Repository<FooEntity> implements IFooRepository {
getFoo() {
// Do stuff
}
}
#Module({
imports: [TypeOrmModule.forRoot(), TypeOrmModule.forFeature([FooRepository]],
// Other module setup
})
export class AppModule {}
It should work with using the InjectRepository decorator where you specify the Repository but then you type is as your interface instead and when testing you just provide the IFooRepository!
Example code:
constructor(#InjectRepository(FooRepository) fooRepository: IFooRepository,
barEventBus: EventBus) {
Edit: This answer is crap, that abstract-class-as-interface hack used does not work out as the defined methods seem to be optional to implement despite being marked as abstract.
Well, kind of got it working. Based on this answer https://stackoverflow.com/a/74561702/913136 I used an abstract class as interface (you can actually implement it) for not being required to pass strings around as tokens. Only drawback is the misuse of the abstract class. Not sure yet if I like it.
Using an actual interface in the same way seems not to be possible unfortunately. Urgh.
#Module({
imports: [
TypeOrmModule.forRoot({
...dataSource.options,
autoLoadEntities: true,
}),
TypeOrmModule.forFeature([Listing]),
],
controllers: [ViewListingController],
providers: [
{
provide: ListingRepository,
useClass: TypeOrmListingRepository,
},
],
})
makeshift interface:
import { Listing } from "./Listing";
export abstract class ListingRepository {
abstract findMostRecent: () => Promise<Listing[]>;
}
implementation:
import { Listing, ListingRepository } from "../../Domain";
import { Injectable } from "#nestjs/common";
import { Repository, DataSource } from "typeorm";
#Injectable()
export class TypeOrmListingRepository
extends Repository<Listing>
implements ListingRepository
{
constructor(private dataSource: DataSource) {
super(Listing, dataSource.createEntityManager());
}
findMostRecent() {
return this.find({});
}
}
import { Controller, Get } from "#nestjs/common";
import { ListingRepository } from "../Domain";
#Controller("listings")
export class ViewListingController {
constructor(private readonly listingRepo: ListingRepository) {}
#Get("/most-recent")
listMostRecent() {
return this.listingRepo.findMostRecent();
}
}
I have simple custom decorator:
export const User: () => ParameterDecorator = createParamDecorator(
(data: any, req): UserIdentity => {
const user = getUser(req);
return user;
},
);
And now, I need to validate if we have email in user object.
The problem is that I can't update my current decorator.
Could I extend my current decorator?
Create a new decorator based on the previous one or create a new decorator and combine it?
Yes, you can do "decorator composition" with Nest, but this might not be a perfect solution for your case, depending on what you intend to do when user has no email property.
As per the example from the documentation:
import { applyDecorators } from '#nestjs/common';
export function Auth(...roles: Role[]) {
return applyDecorators(
SetMetadata('roles', roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized"' }),
);
}
In this example, Auth is a decorator that can be used to combine all the one passed in applyDecorators.
Thus, I'd recommend extending your decorator using a pipe.
As stated by the documentation:
Nest treats custom param decorators in the same fashion as the built-in ones (#Body(), #Param() and #Query()). This means that pipes are executed for the custom annotated parameters as well (in our examples, the user argument). Moreover, you can apply the pipe directly to the custom decorator:
#Get()
async findOne(#User(new ValidationPipe()) user: UserEntity) {
console.log(user);
}
In this example, User is a custom parameter decorator. And ValidationPipe is passed, but you can imagine passing any pipe.
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'
}
}