Search code examples
typescriptservicemodulenestjsnodemailer

Nestjs does not see the Module/Service


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.


Solution

  • 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