230102월_공부일지
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);
}
}