서버가 제공하는 핵심 기능은
전달 받은 데이터를 어떻게 비즈니스 로직으로 해결하는가.
(이런걸 비즈니스 로직이라고 하나보군.)
앱이 제공하고자 하는 핵심 기능,
즉 비즈니스 로직을 수행하는 역할을 프로바이더, provider라고 한다.
단일 책임 원칙, single responsibility principle, SRP에
부합하게 하려면 따로 모듈화 하는게 좋겠지!
프로바이더는
서비스, 저장소(repository), 팩터리(Factory), 헬퍼(helper)등
여러가지 형태로 구현이 가능 하다.
Nest에서 제공하는 프로바이더의 핵심은
의존성을 주입할 수 있다는 점.
컨트롤러 구조를 보면
Controller-constructor로 UserService 생성
->UserService.remove 등 생성자로 생성한 객체 내 method로 비즈니스 로직 수행.
으로 이루어져있다.
만약 @Injectable() 데커레이터를 쓴다면
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
...
remove(id: number) {
return 'This action removes a #${id} user';
}
}
식으로, class를 선언하는 ts에서
이 데커레이터를 선언하여
다른 어떤 Nest 컴포넌트에서도 주입할 수 있는 프로바이더가 된다.
별도의 스코프를 지정해주지 않으면 일반적으로 싱글턴 인스턴스가 생성된다.
싱글턴 인스턴스는
일단 하나를 생성하고
그것 자체를 지들끼리 공유한다는 말인거같다.
어디서든 주입할수 있다는건 어디서든 호출이 가능하다는 말 같고.
프로바이더 인스턴스 역시
모듈에서 사용할수 있도록 등록해줘야한다.
Module 파일 안에 provider 카테고리안에 들어있을 것.
(확실히
users 폴더 안에 UserModule이 있고
users/entitis 라는 폴더 안에 @Injectable() 선언된 UserService가 있음.)
(근데 Appmodule 이라는게 따로 있어서,
각자 연결되어있는 영역과 범위를 따로 두나보군.)
음.
생성자 기반으로 하면
Base 안에
service A, service B를 달고 싶고.
service B가 base 기반으로 추가 기능을 쓰되
service A의 속성을 쓰고 싶다면.
super(_serviceA) 등을 선언해줘야한다.constructor(private readonly _serviceA:ServiceA){super(_surviceA)@Inject(ServiceA) private readonly serviceA: ServiceA;이 데커레이터는 인수로 타입(클래스 이름), 문자열, 심벌이 가능하지만
대개 @Injectable 이 선언된 클래스는 클래스 이름 타입이면 됨.
nest g s Users 명령어로 UserService 프로바이더 생성.
이후에...
쭉 뼈대 만들고
채우는 걸로 구성했길래
돌아가나 끝까지 한번 해봄.
후...
하드 코딩하라는 말을 잘 못알아들어서
500 에러 뜨면서 우와 500에러다 싶지만
다 알려주는데 500에러??? 하면서 함
아무튼
app.controller
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UserLoginDto } from './dto/user-login.dto';
import { VerifyEmailDto } from './dto/verify-email.dto';
import { UserInfo } from './UserInfo';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
@Post()
async createUser(@Body() dto: CreateUserDto): Promise<void> {
console.log(dto);
const { name, email, password } = dto;
await this.usersService.createUser(name, email, password);
}
constructor로 프로바이더 private하게 만들어서
본래 생성하는 CreateUser 함수를...
dto에서 가져와 변수로 선언하고
프로바이더 내 메소드로 연결하고 있음.
이메일 검증시 필요한 토큰 형식을 uuid로 한다고 함.
npm i uuid
npm i --save-dev @types/uuid
로 함.
그리고 UsersService를
폴더 내 파일에서 구현하는데 동작이
import * as uuid from 'uuid';
import { Injectable } from '@nestjs/common';
import { EmailService } from 'src/email/email.service';
@Injectable()
export class UsersService {
async createUser(name: string, email: string, password: string) {
await this.checkUserExists(email);
const signupVerifyToken = uuid.v1();
await this.saveUser(name, email, password, signupVerifyToken);
await this.sendMemberJoinEmail(email, signupVerifyToken);
}
private checkUserExists(email: string) {
return false; //TODO: DB 연동후 구현
}
private saveUser(
name: string,
email: string,
password: string,
signupVerifyToken: string,
) {
return; // TODO: DB 연동후 구현.
}
private async sendMemberJoinEmail(email: string, signupVerifyToken: string) {
await this.emailService.sendMemberJoinVerification(
email,
signupVerifyToken,
);
}
}
외부 이메일 서비스 고를시 고려할 점
등이 있지만 가장 중요한건 이메일 전송 자체의 안정성.
무료로 이메일 전송을 해주는 nodemailer을 써볼 예정.
npm i nodemailer
npm i @types/nodemailer --save-dev
위에 UsersService는 유저 정보 저장, 조회를 하지만,
Email 처리는 EmailService 프로바이더로 관리해보자.
nest g s Email 로 프로바이더 폴더를 생성하고,
(spec은 테스트 코드로 사용하라고 줌.)
UsersService에서 sendMemberJoinEmail을 구현해야하니
import { EmailService } from 'src/email/email.service';
@Injectable()
export class UsersService {
constructor(private emailService: EmailService) {}
...
private async sendMemberJoinEmail(email: string, signupVerifyToken: string) {
await this.emailService.sendMemberJoinVerification(
email,
signupVerifyToken,
);
}
}
그리고 EmailService 에서
nodemailer로 이메일을 전송하는 걸 구현해보자.
import Mail = require('nodemailer/lib/mailer');
import * as nodemailer from 'nodemailer';
import { Injectable } from '@nestjs/common';
interface EmailOptions {
to: string;
subject: string;
html: string;
}
@Injectable()
export class EmailService {
private transporter: Mail;
constructor() {
this.transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: '하드코딩해야하는 이메일.',
pass: '하드코딩해야하는 비번.',
},
});
}
async sendMemberJoinVerification(
emailAddress: string,
signupVerifyToken: string,
) {
const baseUrl = 'http://localhost:3000';
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);
}
}
이후 장에는 하드코딩하지 않고 환경변수로 관리할 것.
흠..
nodemailer에서 Mail이라는 인터페이스를 가져와서
멤버 변수, transporter를 만들어주었으니
그 안을 nodemailer method로 생성하고
함수에서 받은 매개변수에 따라
url을 구성하는데 쿼리 형식에 따라 채워서
메일 본문은 mailOptions라는 변수로
내가 설정했던 MailOptions라는 interface에 따라
html로 보내주는군....
아마 지금은 curl 할때 형식에 맞춰 보내긴 하지만
실제로 내가 보낸 인수를 쓰고 있진 않을 거임.
잘 도착하는거 확인했음.
curl http://localhost:3000/users -H "Content-Type: application/json" -X POST -d '{"name":"YOUR_NAME", "email":"하드코딩한 이메일", "password":"하드코딩한 비밀번호"}'