For several days I have been trying to solve the problem with the fact that Nest does not see the service/module for me, which can be seen in the error below, I tried to look for a solution, I even asked ChatGPT and he said that everything is implemented correctly, so I finally decided to ask you for advice, maybe one of you knows what I should do change it to make it work and what am i doing wrong?
Error:
[Nest] 18316 - 24.04.2023, 15:35:59 ERROR [ExceptionHandler] Nest can't resolve dependencies of the MailService (MailerService, ?). Please make sure that the argument dependency at index [1] is available in the AuthModule context.
Potential solutions:
- Is AuthModule a valid NestJS module?
- If dependency is a provider, is it part of the current AuthModule?
- If dependency is exported from a separate @Module, is that module imported within AuthModule?
@Module({
imports: [ /* the Module containing dependency */ ]
})
AuthModule Code:
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { HttpModule } from '@nestjs/axios';
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { GoogleStrategy } from './strategy/google.strategy';
import { FacebookStrategy } from './strategy/facebook.strategy';
import googleConfig from '../config/google.config';
import facebookConfig from '../config/facebook.config';
import { MailService } from './mail/mail.service';
import { MailModule } from './mail/mail.module';
import { UsersModule } from '../users/users.module';
import { User } from '../users/entities/user.entity';
import { Profile } from '../users/entities/profile.entity';
@Module({
imports: [
HttpModule,
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('jwt.secretKet'),
signOptions: { expiresIn: configService.get('jwt.expirationTime') },
}),
}),
UsersModule,
ConfigModule.forFeature(googleConfig),
ConfigModule.forFeature(facebookConfig),
TypeOrmModule.forFeature([User, Profile]),
MailModule,
],
controllers: [AuthController],
providers: [
AuthService,
GoogleStrategy,
FacebookStrategy,
MailService,
],
exports: [AuthService],
})
export class AuthModule {}
AuthService Code:
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '../users/user.service';
import { MailService } from './mail/mail.service';
import { compare } from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private readonly userService: UserService,
private readonly jwtService: JwtService,
private readonly mailService: MailService,
) {}
async login(user: any) {
const socialUser = await this.userService.findOrCreateSocialUser(user);
await this.mailService.sendMail({
to: user.email,
subject: 'Verify your email',
template: 'email-verification',
context: {
name: user.name,
},
});
return socialUser;
}
}
MailModule Code:
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MailerModule, MailerService } from '@nestjs-modules/mailer';
import { MailService } from './mail.service';
import * as handlebars from 'handlebars';
import mailConfig from '../../config/mail.config';
@Module({
imports: [
ConfigModule.forFeature(mailConfig),
MailerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
transport: {
host: configService.get('mail.host'),
port: configService.get('mail.port'),
secure: configService.get('mail.secure') === 'true',
auth: {
user: configService.get('mail.user'),
pass: configService.get('mail.password'),
},
},
defaults: {
from: `"${configService.get('APP_NAME')}" <${configService.get(
'mail.user',
)}>`,
},
template: {
dir: './templates',
adapter: new HandlebarsAdapter(), // lub inny adapter szablonów
options: {
strict: true,
},
},
}),
}),
],
controllers: [],
providers: [MailService, MailerService],
exports: [MailService, MailerService],
})
export class MailModule {}
export class HandlebarsAdapter {
compile(mail: any, callback: any, mailerOptions: any) {
const template = handlebars.compile(mail.data.html);
const html = template(mail.data.context);
mail.data.html = html;
callback();
}
}
MailService Code:
import { Injectable } from '@nestjs/common';
import { MailerService } from '@nestjs-modules/mailer';
import { ConfigType } from '@nestjs/config';
import mailConfig from '../../config/mail.config';
@Injectable()
export class MailService {
constructor(
private readonly mailerService: MailerService,
private mailConf: ConfigType<typeof mailConfig>,
) {}
async sendMail(options: {
to: string;
subject: string;
template: string;
context: Record<string, unknown>;
}) {
const { to, subject, template, context } = options;
const mailOptions = {
to,
subject,
template: `./${template}`,
context,
};
await this.mailerService.sendMail(mailOptions);
}
}
Mail Config Code:
import { registerAs } from '@nestjs/config';
export default registerAs('mail', () => ({
host: process.env.mail_host,
port: process.env.mail_port,
secure: process.env.mail_secure,
auth: {
user: process.env.mail_user,
pass: process.env.mail_password,
},
}));
Because when I imported, for example, User to be visible in Auth, everything worked normally for me, and when I want to import from a folder that is in the current folder, I get an error and it seems to me that I did everything right.
ConfigType<typeof mailConfig>
is a type, and types are erased by typescript compiler at compile time. To circumvent that limitation, you should use the @Inject()
parameter decorator.
I think that you should do this:
@Inject(mailConfig.KEY) private mailConf: ConfigService<typeof mailConfig>
as the documentation shows here: https://docs.nestjs.com/techniques/configuration