> Backend/NestJS

230102월_공부일지

Janku 2023. 1. 2. 09:21

1. NestJS에서 제공하는 Config 패키지

   => 이전까지는 dotenv패키지를 직접 사용했는데, 이젠, nestJS에서 제공되는 패키지를 사용할 예정

   => cmd: npm i --save @nestjs/config

   =>  

//dotenv
import {ConfigModule} from "@nestjs/config";
import emailConfig from "./config/emailConfig";
import { validationSchema } from './config/validationSchema';

@Module({
    // Nest에서 제공되는 Config 패키지
    // forRoot 매서드는 Dynamic Module을 리턴하는 정적 메서드
    imports: [UsersModule, EmailModule, ConfigModule.forRoot({
        envFilePath: [`${__dirname}/config/env/.${process.env.NODE_ENV}.env`],
        load: [emailConfig], // load 속성을 통해 앞서 구성해둔 configFactory 지정
        isGlobal: true, // 전역 모듈로 사용
        validationSchema // 환경변수의 값에 대한 유효성 검사를 수행하도록 joi를 이용해 유효성 검사 객체를 작성
    })],
    controllers: [ApiController, AppController],
    providers: [AppService],
})
export class AppModule {
}

   =>  NestJS가 제공하는 ConfigModule은 dotenv 파일에서 읽어온 환경 변수 값을 가져오는 프로바이더인 ConfigService가 있고, 이를 사용하기 위해서는 원하는 컴포넌트에 주입해서 사용하면 됨. 

export class AppController {
    constructor(
        private readonly appService: AppService,
        private readonly emailService: EmailService,
        private readonly configService: ConfigService) {
    }

 
    @Get('/db-host-config')
    getDatabaseHostFromConfigService(): string {
      return this.configService.get('DATABASE_HOST')
    }
    ... 하략 ...

 

2. 유저 서비스에 환경 변수 구성하기

   => cmd : npm i @nestjs/config

   => cmd : npm i joi

   => 기존에 src/env 디렉토리 내에 있던 파일들을 src/config/env 디렉토리를 생성하고, 해당 디렉토리로 이동

 

=> 추가적으로 각 .env파일에  해당 내용 추가 

EMAIL_SERVICE=Gmail
EMAIL_AUTH_USER= 지메일 이메일 주소 
EMAIL_AUTH_PASSWORD= 지메일 비번 
EMAIL_BASE_URL= http://localhost:3000

 

 

3. 커스텀 Config 파일 작성 

   => 모든 환경 변수가 .env 파일에 선언되어있지만, EmailConfig와 같이 의미있는 단위로 묶어서 처리하고 싶을 경우 @nestjs/config 패키지에서 제공하는 ConfigModule를 사용. 

 

   => 

import { registerAs } from "@nestjs/config";

export default registerAs( 'email ', () => ({
    service: process.env.EMAIL_SERVICE,
    auth: {
        user: process.env.EMAIL_AUTH_USER,
        pass: process.env.EMAIL_AUTH_PASSWORD
    },
    baseUrl: process.env.EMAIL_BASE_URL
}))


// 설명: @nestjs/config 패키지에서 제공하는 registerAs 함수의 선언을 보면,
// 첫번째 인자로는 토큰을 문자열로 받고, 두번째 인수로는 ConfigFactory 함수를 상속하는 타입 TFacotry의 함수를 받아서 TFacotry의 && ConfigFacotryKeyHost를 합친 타입의 함수를 리턴함.

// emailConfig.ts를 설명하면, email 이라는 토큰으로 ConfigFactory 를 등록할 수있는 함수라고 이해할 것.

 

4. 동적 ConfigModule 등록 

   => NestJS 기본 빌드 옵션은 .ts 파일 외에 asset은 제외하도록 되어 있기 때문에, .env 파일을 out 디렉토리에 복사할 수 있도록, NEST-CLI.JSON에서 옵션을 바꿔줘야함. 

 

... 생략 
"compilerOptions": {
  "assets": [
    {
      "include": "./config/env/*.env",
      "outDir": "./dist"
    }
  ]
}

 

=> app.module.ts 에 validationSchema를 (./config/validationSchema) 에 생성 

validationSchema // 환경변수의 값에 대한 유효성 검사를 수행하도록 joi를 이용해 유효성 검사 객체를 작성
import * as Joi from 'joi';

export const validationSchema = Joi.object({
    EMAIL_SERVICE: Joi.string().required(),
    EMAIL_AUTH_USER: Joi.string().required(),
    EMAIL_AUTH_PASSWORD: Joi.string().required(),
    EMAIL_BASE_URL: Joi.string().required().uri(),

})

 

   => .gitignore 에 env 파일 안올라가게 설정 ( 깃에 올리면 안되는 환경 변수 ) 

# local env files
*.env
.env.local
.env.*.local
Env.json

 

=> 가끔 깃에 올리면 안되는 환경 변수들도 있는데, 이런 변수들은 Nest가 구동되기 전에 서버가 프로비저닝되는 과정에서 다른 비밀번호 관리 시스템을 읽어와서 소스 코드 내의 .env 파일을 수정하도록 하는 방법을 쓰거나, 미리 컴파일 된 dist 파일을 다른 저장소에서 가져와서 수정하여 구동하는 방법을 사용해야 함. 

 

=> 이제, emailConfig를 우리가 사용하려고 하는 곳에 주입받아 사용 할 수 있다. 

import {Injectable, Inject} from '@nestjs/common';
import Mail = require('nodemailer/lib/mailer');
import * as nodemailer from 'nodemailer'
import * as process from "process";

import {ConfigType} from "@nestjs/config";
import emailConfig from "../config/emailConfig";


interface EmailOptions {
    to: string,
    subject: string,
    html: string
}

@Injectable()
export class EmailService {
    private transporter: Mail;
    
    // before
    // constructor() {
    //     this.transporter = nodemailer.createTransport({
    //         service: 'gmail',
    //         auth: {
    //             // user:  'G-MAIL 이메일 주소',
    //             // pass: 'G-MAIL 이메일 비번 > 2차 비번'
    //             user:  process.env.GOOGLE_EMAIL,
    //             pass: process.env.GOOGLE_PW
    //             // nodemailer는 간단한 이메일 전송 테스트만을 위해 작성되었기 때문에, 2단계 인증 활성화 및 앱 비밀번호를 진행해야됨
    //         }
    //     })
    // }

    // after
    constructor(
        @Inject(emailConfig.KEY) private config: ConfigType<typeof emailConfig>
        // 주입받을 떄는 @Inject decorator의 토큰을 앞서 만든 ConfigFactory의 KEY 인 'email' 문자 열로 넣어주기
    ) {
        this.transporter = nodemailer.createTransport({
            service: config.service,
            auth: {
                user: config.auth.user,
                pass: config.auth.pass
            }
        })
    }

ß
    async sendMemberJoinVerification(emailAddress: string, signupVerifyToken: string) {
        const baseUrl = this.config.baseUrl  // .env파일에 있는 값들을 사용 
        const url = `${baseUrl}/users/email-verify?signupVerifyToken=${signupVerifyToken}`;
        const mailOptions: EmailOptions = {
            to: emailAddress,
            subject: '가입인증 메일',
            html: `
                가입확인 버튼을 누르시면 가입인증이 완료 됩니다. <br/>
                <form action="${url}" method="POST">
                <button>가입 확인 버튼</button></form>
            `
        }

        return await this.transporter.sendMail(mailOptions);
    }


}