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
I have a question regarding dynamic modules importing.
Let's say I have #Injectable service which depends on the parameter that came from the configuration:
#Injectable()
export class StatsService {
constructor(#Inject(SERVICE_NAME) private serviceName: string) {}
...
}
Let's say I expose this service through the Dynamic module:
#Module({})
export class CoreServicesModule {
static register(coreConfig: CoreServicesConfig): DynamicModule {
const { serviceName } = coreConfig;
return {
module: CoreServicesModule,
providers: [
{
provide: SERVICE_NAME,
useValue: serviceName
},
StatsService
],
exports: [
StatsService
]
};
}
}
Let's say my application is pretty big, and I have a lot of different modules.
But I need StatsService in every one of them.
So for example to be able to use StatsService in one of the modules i need to do something like:
#Module({
imports: [CoreServicesModule.register({ serviceName: 'test', ... })]
})
export class SomeModule { ... }
And I need to do it for each module...
Do we have a way to do it only once in NestJS?
Or how we can re-use the already registered module?
#Global decorator also not help me here cause I still need to import the module in each place...
#Global() decorator will help you if you are interested in using StatService in all other modules without the need to import that module!
BUT from your code it seems that each module that import CoreServicesModule would also make a small adjustment to it by dynamically specifying the providers it has (such as ValidationPipe).
in that case you should NOT make it #Global() since global modules should be registered only once
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 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'
}
}
I want to design a TypeScript (2.7) module for accessing external IS, let's call it InfoSys. I used the following approach.
I created info-sys.ts which defines a API class and related interfaces and enums, like:
class Api {
constructor(private endpoint: Endpoint) {
// ...
}
}
enum Endpoint {
CONTACTS = "contacts"
}
interface Contact {
name: string;
}
Now I want to export all the stuff under specific name. So I appended the export statement:
export const InfoSys = {
Api,
Endpoint,
Contact
};
When I try to use the module in another file, like:
import { InfoSys } from "info-sys";
// this line throws error: "Cannot find namespace 'InfoSys'"
private api: InfoSys.Api;
// but this line is ok
api = new InfoSys.Api(InfoSys.Endpoint.CONTACTS);
The way that works is the following - to export every piece individually:
export class Api {
constructor(private endpoint: Endpoint) {
// ...
}
}
export enum Endpoint {
CONTACTS = "contacts"
}
export interface Contact {
name: string;
}
and import them all to a single variable:
import * as InfoSys from "info-sys";
But the name of the variable can be whatever. It is not critical for functionality but I want to force developers, who will use the info-sys module, to use a specific name while accessing it (for easier readability and maintainability). How to properly design such module?
You can use namespace:
export namespace InfoSys {
Api,
Endpoint,
Contact
};
In general, this approach should be avoided. But in your case, it is fine as you are delivering things that are tightly related.
If Api is the single entry point to all these, I would also recommend this:
export class InfoSysApi { ... }
export namespace InfoSysApi {
export enum Endpoint = { ... }
export interface Contact { ... }
}
UPDATE:
To make sure I get the point through, DON'T do the following:
export namespace Foo {
export function X() { return 'x' }
export function Y() { return 'y' }
}
Only use export namespace to export "tugged in types", not values.
In TypeScript handbook: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
Although the table says namespace can contain values, it is considered bad practice if you are writing ESM (import/export).
Namespace and ESM are two different mechanisms to achieve similar result.
Don't mix them together.