I'm new with NestJS and trying to understand the patterns.
I configure ConfigModule to use config set in a specific folder. I want to use that ConfigModule in a submodule but It doesn't work.
I have an app.module
app.module.ts
#Module({
imports: [
ConfigModule.forRoot({
envFilePath: ['.env.local', '.env'],
isGlobal: true,
expandVariables: true,
load: [dbConfiguration, jwtConfiguration],
}),
DatabaseModule,
AuthModule,
UsersModule,
],
controllers: [AppController],
providers: [],
exports: [ConfigModule]
})
export class AppModule {
}
auth.module.ts
#Module({
imports: [
UsersModule,
PassportModule,
ConfigModule,
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secretOrPrivateKey: configService.get('jwt').secret,
signOptions: {
expiresIn: 3600,
},
}),
inject: [ConfigService],
}),
],
providers: [AuthService, JwtStrategy],
exports: [AuthService]
})
export class AuthModule {}
jwt.strategy.ts
import { PassportStrategy } from "#nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
import {ConfigService} from "#nestjs/config";
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
console.log(configService);
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('jwt').secret,
})
}
async validate(payload: any) {
return {userId: payload.sub, email: payload.email}
}
}
In the constructor, configService is undefined.
I import ConfigModule in auth.module that contain JwtStrategy.
What did I missed?
Missed the #Injectable() in JwtStrategy
Related
I have this code:
import { Module, Injectable } from '#nestjs/common'
#Injectable()
export class PrismaService {
constructor() {
console.log('init original')
}
}
#Injectable()
export class PrismaServiceMock {
constructor() {
console.log('init mock')
}
}
#Module({
providers: [
{
provide: PrismaService,
useClass: PrismaService,
},
],
exports: [PrismaService],
})
export class PrismaModule {}
#Module({
imports: [PrismaModule],
providers: [
{
provide: PrismaService,
useClass: PrismaServiceMock,
},
],
})
export class AppModule {}
Because of
providers: [
{
provide: PrismaService,
useClass: PrismaServiceMock,
},
],
I expect PrismaServiceMock to be instantiated. However, both PrismaService are still created. What do I miss?
you can do something like this:
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: PrismaService,
useValue: {
onModuleInit: jest.fn()
},
},
]
});
or mock your database driver:
jest.mock('mysql')
or:
jest.spyOn(mysql,'connect').mockImplemetation()
or in the case of MongoDB, do something like:
const module: TestingModule = await Test.createTestingModule({
providers: [
{ provide: 'DatabaseConnection', useValue: {} }
]
});
There are plenty of articles showing how to inject providers into dynamic module but all of them only show examples using their exported services, none with middleware. Using the exact same method as used with services for middleware fails. How can I create reusable middleware that requires providers to be injected?
Example middleware, requiring injection of a UserService provider
#Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(#Inject(USER_SERVICE) private readonly userService: UserService) {
console.log('userService findAll: ', userService.findAll());
}
use(req: any, res: any, next: () => void) {
console.log('AuthMiddleware called!');
next();
}
}
Example module containing the middleware:
#Module({
providers: [AuthService, AuthMiddleware],
imports: [ExtModule],
})
export class AuthModule extends createConfigurableDynamicRootModule<
AuthModule,
AuthModuleOptions
>(AUTH_OPTIONS, {
providers: [
{
provide: AUTH_SECRET,
inject: [AUTH_OPTIONS],
useFactory: (options: AuthModuleOptions) => options.secret,
},
{
provide: USER_SERVICE,
inject: [AUTH_OPTIONS],
useFactory: (options: AuthModuleOptions) => options.userService,
},
],
controllers: [AuthController],
}) {}
Now importing the module and trying to use the middleware:
#Module({
imports: [
ConfigModule.forRoot(),
AuthModule.forRootAsync(AuthModule, {
imports: [ConfigModule, UserModule],
inject: [ConfigService, UserService],
useFactory: (config: ConfigService, userService: UserService) => {
return {
secret: config.get('AUTH_SECRET_VALUE'),
userService,
};
},
}) as DynamicModule,
UserModule,
],
controllers: [UserController],
providers: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('*');
}
}
Now when initializing the AuthModule dependencies the middleware class is clearly instantiated correctly, the userService.findAll being logged:
userService findAll: []
So far so good, and this works fine for services.
But the problem is that when then actually using the middleware in configure(), the injected context is not used
...\app\node_modules\#nestjs\core\injector\injector.js:193
throw new unknown_dependencies_exception_1.UnknownDependenciesException(wrapper.name, dependencyContext, moduleRef);
^
Error: Nest can't resolve dependencies of the class AuthMiddleware {
constructor(userService) {
this.userService = userService;
console.log('userService findAll: ', userService.findAll());
}
use(req, res, next) {
console.log('AuthMiddleware called!');
next();
}
} (?). Please make sure that the argument Symbol(USER_SERVICE) at index [0] is available in the AppModule context.
I've ended up getting it to work, mostly by re-exporting the injected providers. After trying different combinations, the working one is as follows
Static dependencies (e.g. external libraries) need to be imported and re-exported inside the module decorator
Dynamic dependencies need to be imported and re-exported inside createConfigurableDynamicRootModule
Exporting the Middleware class seems to have no effect in any way
#Module({
providers: [],
imports: [ExtModule],
exports: [ExtModule],
})
export class AuthModule extends createConfigurableDynamicRootModule<
AuthModule,
AuthModuleOptions
>(AUTH_OPTIONS, {
providers: [
{
provide: AUTH_SECRET,
inject: [AUTH_OPTIONS],
useFactory: (options: AuthModuleOptions) => options.secret,
},
{
provide: USER_SERVICE,
inject: [AUTH_OPTIONS],
useFactory: (options: AuthModuleOptions) => options.userService,
},
],
exports: [
{
provide: AUTH_SECRET,
inject: [AUTH_OPTIONS],
useFactory: (options: AuthModuleOptions) => options.secret,
},
{
provide: USER_SERVICE,
inject: [AUTH_OPTIONS],
useFactory: (options: AuthModuleOptions) => options.userService,
},
],
}) {}
#Module({
imports: [],
providers: [SupertokensService, AuthService],
exports: [],
controllers: [AuthController],
})
export class AuthModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('*');
}
static forRoot({
connectionURI,
apiKey,
appInfo,
}: AuthModuleConfig): DynamicModule {
return {
providers: [
{
useValue: {
appInfo,
connectionURI,
apiKey,
},
provide: ConfigInjectionToken,
},
],
exports: [],
imports: [],
module: AuthModule,
};
}
}
The problem with this implementaion I can't use env variables, so I need useFactory to pass ConfigService. Can somebody do that, and give some explanation.
I figure out how to make this work, unfortunately it only works with nest.js version 9 (current latest version). First you need to create a new file. For example auth.module-definition.ts. Now in this file we need to create ConfigurableModuleBuilder.
import { ConfigurableModuleBuilder } from '#nestjs/common';
import { AuthModuleConfig } from './config.interface';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<AuthModuleConfig>()
.setClassMethodName('forRoot')
.build();
we need to set setClassMethodName('forRoot'), when we set forRoot it will create two methods, forRoot and forRootAsync. The next step is to extend our created ConfigurableModuleClass. it should look something like this
import { MiddlewareConsumer, Module } from '#nestjs/common';
import { AuthMiddleware } from './auth.middleware';
import { SupertokensService } from './supertokens/supertokens.service';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { ConfigurableModuleClass } from './auth.module-definition';;
#Module({
imports: [],
providers: [SupertokensService, AuthService],
controllers: [AuthController],
exports: [AuthService],
})
export class AuthModule extends ConfigurableModuleClass {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('*');
}
}
And that's actually it, so from now on we have forRootAsync and we get reigister it in app.module.ts
import { MiddlewareConsumer, Module, NestModule } from '#nestjs/common';
import { AuthModule } from './auth/auth.module';
import { ConfigModule, ConfigType } from '#nestjs/config';
import authConfig from './auth/auth.config';
#Module({
imports: [
AuthModule.forRootAsync({
inject: [authConfig.KEY],
imports: [ConfigModule.forFeature(authConfig)],
useFactory: (config: ConfigType<typeof authConfig>) => {
return {
connectionURI: config.CONNECTION_URI,
appInfo: {
appName: config.appInfo.APP_NAME,
apiDomain: config.appInfo.API_DOMAIN,
websiteDomain: config.appInfo.WEBSITE_DOMAIN,
apiBasePath: config.appInfo.API_BASE_PATH,
websiteBasePath: config.appInfo.WEBSITE_BASE_PATH,
},
};
},
})
],
controllers: [],
providers: [
],
})
export class AppModule implements NestModule {
}
here I am using Nest.js Config, but you don't need to, so use it how you want.
I know that my english is not the best, so if you still do not understand you can check these sources https://docs.nestjs.com/fundamentals/dynamic-modules#configurable-module-builder
https://trilon.io/blog/nestjs-9-is-now-available#Configurable-module-builder
Okay so I'm using TYPEORM_ prefixed environment variables to register TypeOrmModule into my app. Then I'm loading entities using .forFeature() like so:
#Module({
imports: [TypeOrmModule.forFeature([Foo])],
controllers: [FooController],
providers: [FooService],
exports: [TypeOrmModule],
})
export class FooModule {}
No matter whether I use forRoot() or forRoot({ autoLoadEntities: true }) in AppModule, I get RepositoryNotFoundError: No repository for "Foo" was found. Looks like this entity is not registered in current "default" connection? error while working with the dev server. Any idea what's going on? My service looks like:
#Injectable()
export class FooService {
constructor(#InjectRepository(Foo) private readonly fooRepository: Repository<Foo>) {}
}
I tried the followings as single and/or combined, still didn't solve the issue.
Put a name in #Entity() decorator.
#Entity('foo')
export class Foo {}
Try forRootAsync().
#Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: async () =>
Object.assign(await getConnectionOptions(), { autoLoadEntities: true }),
}),
FooModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Pass 'default' to forFeature().
#Module({
imports: [TypeOrmModule.forFeature([Foo], 'default')],
controllers: [FooController],
providers: [FooService],
exports: [TypeOrmModule],
})
export class FooModule {}
Try using entities key in forRoot().
#Module({
imports: [
TypeOrmModule.forRoot({ entities: [Foo] }),
FooModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
try to define connectionName inside getConnectionOptions
example:
#Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: async () =>
Object.assign(await getConnectionOptions('default'), { autoLoadEntities: true }),
}),
FooModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
I'm having a problem with overriding a provider in a test context. I know I'm missing something blindingly obvious, but I've tried loads of permutations and none have worked, so I'm putting this out to the hive mind.
I have the following structure:
shared.module.ts
#Module({
providers: [
DatabaseProvider,
ConfigService,
CacheService
],
exports: [
DatabaseProvider,
ConfigService
CacheService
],
imports: [HttpModule],
})
where DatabaseProvider is defined as:
const DatabaseProvider = {
provide: "MongoConnection",
inject: [ConfigService],
useFactory: async (config: ConfigService): Promise < typeof mongoose | Object > => {
return await mongoose.connect(config.getString("MONGO_URI"), { useNewUrlParser: true });
}
};
my.module.ts
#Module({
imports: [SharedModule, HttpModule],
exports: [MyService],
controllers: [MyController],
providers: [MyService],
})
app.module.ts
#Module({
imports: [
SharedModule,
MyModule
],
providers: [
...
],
controllers: [
...
]
})
my.service.ts
export class MyService {
constructor(
#Inject("MongoConnection") private readonly dbConnection: Connection,
private _cache: CacheService
) { }
...
}
my.spec.ts
let cacheService = {...};
this.db = {...};
const module = await Test.createTestingModule({
imports: [SharedModule, MyModule, HttpModule]
})
.overrideProvider(CacheService)
.useValue(cacheService)
.overrideProvider("MongoConnection")
.useValue(this.db)
.compile();
My issue is that the CacheService appears to be being overridden (it's using redis-mock defined in cacheService, instead of the default redis implementation in CacheService), but the MongoConnection is not - it is still calling the original DatabaseProvider from shared.module.ts.
Can someone point out what I'm missing?