https://castle0312.tistory.com/16
[Node.js] SNS 만들기 - Model 만들기
https://castle0312.tistory.com/15 [Node.js] SNS 만들기 - 라우터 만들기 https://castle0312.tistory.com/14 [Node.js] SNS 만들기 - Express로 서버 구축 학습 목표 Node.js와 Express로 기본적인 서버 만들기 들어가기 전 해당
castle0312.tistory.com
들어가기 전
이전 글인 Model 만들기에 이어 실질적인 동작에 해당하는 Controller를 어떻게 만드는지 알아보자.
1. Controller?
Controller란 이전 글에서 말한 MVC 패턴에서 C에 해당하는 부분이다. 쉽게 말하면 우리가 게시글 목록을 불러오는 요청을 유저가 보내면 서버에서 실질적으로 처리하는 부분을 말한다. Controller가 요청을 처리하다가 데이터베이스를 활용해야 하면 Model을 통해서 데이터를 가공하고 요청에 맞게 동작을 완료하면 해당 결과를 View로 보내면 유저가 결과를 보는 것이다. 흔히들 말하는 백엔드 동작의 대부분이 여기서 이루어진다고 생각하면 된다.
2. Node.js 에서의 Controller 파일 예시 및 동작 과정
require("dotenv").config();
const users = require("../models/signupModel");
const jwt = require("jsonwebtoken"); //JWT 토큰 발급
const dotenv = require("dotenv");
const bcrypt = require("bcryptjs"); // 비밀번호 해쉬로 변경을 위한 라이브러리
const nodemailer = require("nodemailer"); // 이메일 인증용 라이브러리
dotenv.config();
const loginUser = async (req, res) => {
const { userID, password } = req.body;
try {
const user = await users.User.findOne({ where: { userID: userID } });
if (!user) {
console.log(`${userID}는 없는 아이디 입니다`);
return res
.status(200)
.json({ result: false, message: "존재하지 않는 아이디입니다" });
}
const isPasswordCorrect = await bcrypt.compare(password, user.password);
if (!isPasswordCorrect) {
console.log("비밀번호오류");
return res.status(200).json({ result: false, message: "비밀번호 오류" });
}
const secret = process.env.jwtSecretkey; // jwt용 시크릿 키(.env 파일에 환경변수 처리됨)
const token = jwt.sign({ userID: user.userID }, secret, {
expiresIn: "24h",
});
console.log(`${user.userID}님이 로그인했습니다.`);
//로그인 시 토큰을 발행하고 쿠키에 담아서 유저에게 보내준다
res
.cookie("token", token, {
httpOnly: true, //콘솔창에서 토큰을 수정하는거 방지하는 옵션
secure: false, // HTTPS에 해당하면 true로 설정
maxAge: 24 * 60 * 60 * 1000, // 24h과 동일한 의미
})
.status(200)
.json({
result: true,
message: `${user.user}님이 로그인했습니다.`,
userID: user.userID,
});
} catch (err) {
console.log(err);
res.status(500).json({ result: false, message: "Server error" });
}
};
const findUser = async (req, res) => {
console.log(req.body.email);
try {
const findEmail = await users.User.findOne({
where: {
email: req.body.email,
},
});
if (!findEmail) {
res.status(202).json({ result: false, message: "없는 email입니다." });
} else {
// nodemailer 설정
const transporter = nodemailer.createTransport({
service: "naver",
auth: {
user: "hanium124@naver.com",
pass: process.env.nodemailerPassword,
},
});
// 인증번호 생성 및 저장
const verificationCode = Math.floor(100000 + Math.random() * 900000);
// 이메일 전송 설정
const mailOptions = {
from: "hanium124@naver.com",
to: req.body.email,
subject: "인증번호",
text: `인증번호: ${verificationCode}`,
};
// 이메일 전송
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.log(error);
res.status(400).send("이메일 전송 실패");
} else {
console.log("Email sent: " + info.response);
res.status(200).json({
result: true,
message: verificationCode.toString(),
userID: findEmail.userID,
});
}
});
}
} catch (err) {
console.error(err);
res
.status(404)
.json({ result: false, message: "서버에 문제가 생겼습니다." });
}
};
const findPassword = async (req, res) => {
try {
const findUser = await users.User.findOne({
where: { userID: req.body.userID, email: req.body.email },
});
if (!findUser) {
res.status(202).json({ result: false, message: "잘못된 정보입니다." });
} else {
const transporter = nodemailer.createTransport({
service: "naver",
auth: {
user: "hanium124@naver.com",
pass: process.env.nodemailerPassword,
},
});
// 인증번호 생성 및 저장
const verificationCode = Math.floor(100000 + Math.random() * 900000);
// 이메일 전송 설정
const mailOptions = {
from: "hanium124@naver.com",
to: req.body.email,
subject: "인증번호",
text: `인증번호: ${verificationCode}`,
};
console.log(verificationCode);
// 이메일 전송
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.log(error);
res.status(400).send("이메일 전송 실패");
} else {
console.log("Email sent: " + info.response);
res
.status(200)
.json({ result: true, message: verificationCode.toString() });
}
});
}
} catch (err) {
console.error(err);
res.status(404).json({ result: false, message: "서버 에러" });
}
};
const updatePassword = async (req, res) => {
console.log(req.body.userID);
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(req.body.password, saltRounds);
try {
await users.User.update(
{
password: hashedPassword,
},
{ where: { userID: req.body.userID } }
);
res
.status(200)
.json({ result: true, message: "비밀번호 변경에 성공하였습니다" });
} catch (err) {
res
.status(500)
.json({ resutl: false, message: "비밀번호 변경에 실패하였습니다." });
}
};
module.exports = {
loginUser,
findUser,
findPassword,
updatePassword,
};
위 코드가 Controller 파일의 예시이자 유저의 로그인과 관련된 API들이 정의된 코드이다. 하나씩 보자면 먼저 로그인과 관련된 Model을 불러오고 그외 필요한 라이브러리들을 선언하고 있다. 여기서는 JWT 토큰 , 비밀번호 암호화, 이메일 인증 라이브러리를 사용하고 있는데 자세한건 다른 글에서 알아보도록 하고 나머지 코드를 보자. 먼저 여기서는 총 4개의 API(함수)가 있다.
const loginUser = async (req, res) => {
const { userID, password } = req.body;
try {
const user = await users.User.findOne({ where: { userID: userID } });
if (!user) {
console.log(`${userID}는 없는 아이디 입니다`);
return res
.status(401)
.json({ result: false, message: "존재하지 않는 아이디입니다" });
}
const isPasswordCorrect = await bcrypt.compare(password, user.password);
if (!isPasswordCorrect) {
console.log("비밀번호오류");
return res.status(401).json({ result: false, message: "비밀번호 오류" });
}
const secret = process.env.jwtSecretkey; // jwt용 시크릿 키(.env 파일에 환경변수 처리됨)
const token = jwt.sign({ userID: user.userID }, secret, {
expiresIn: "24h",
});
//로그인 시 토큰을 발행하고 쿠키에 담아서 유저에게 보내준다
res
.cookie("token", token, {
httpOnly: true, //콘솔창에서 토큰을 수정하는거 방지하는 옵션
secure: false, // HTTPS에 해당하면 true로 설정
maxAge: 24 * 60 * 60 * 1000, // 24h과 동일한 의미
})
.status(200)
.json({
result: true,
message: `${user.user}님이 로그인했습니다.`,
userID: user.userID,
});
} catch (err) {
console.log(err);
res.status(500).json({ result: false, message: "Server error" });
}
};
ㅠ위 코드가 로그인을 처리하는 함수이다. async는 해당 함수가 비동기적으로 처리된다는 JavaScript의 문법이고 요청을 받은 부분인 req와 결과를 보낼 res로 인수가 있다. 먼저 req.body로 유저가 보낸 id와 password를 알아낸다. 그리고 아까 위에서 선언한 Model을 이용하여 먼저 id가 데이터베이스에 있는지 즉 회원가입이 된 유저인지 확인하고 맞다면 password 검사로 넘어간다. password검사에서는 받은 password값을 암호화 하고 해당 값을 데이터베이스에 있는 값과 같은지 비교한다. 이 과정까지 통과를 하면 JWT token이라는 것을 발급하여 유저에게 다시 보낼때 같이 보내도록 한다.
* 이 코드에서는 아이디가 없을 때, 비밀번호가 틀렸을 때의 반환 결과를 다르게 하고 있는데 요즘에는 보안적인 측면때문에 어디가 틀렸든 상관없이 틀리면 같은 결과를 반환한다. 이 부분은 개발자의 마음대로 하면되지만 유저 입장에서는 위 코드 처럼하는게 좋을거 같고 보안적인 측면에서는 결과를 똑같이 보내는게 좋을거 같다는 생각이다.(크게 상관은 없겠지만)
이때 로그인 성공시 반환 부분을 보면 쿠키에 토큰을 담아서 보내고 상태 코드는 200으로 보낸다. 상태 코드는 HTTP 통신의 상태 코드를 보내며 상황에 맞게 설정하여 보내면 된다. 그런 다음 보낼 데이터들을 json으로 만들어 보내면 최종적으로 유저에게 API요청의 결과가 보내지는 것이다.
다른 API들도 각자 원하는 동작에 맞춰서 Model을 참조하여 결과를 res에 담아서 return하면 Controller의 동작이 완료된다.
정리
이번 글까지 MVC 패턴에 따른 Node.js에서의 큰 틀에서의 흐름을 살펴 보았다. 즉 지금까지의 글을 따라하면 큰 틀에서의 서버 측 구조는 갖춘 것이다. 이제 더 자세한 동작들을 만들면 백엔드의 코드는 완성이 되는 것인데 다음 글에서는 이러한 과정에서 사용되는 여러가지 기능? 기술?들에 대해 알아보자
'Node.js(Express)' 카테고리의 다른 글
| [Node.js] JWT 사용하기 - access token, refresh token (0) | 2024.01.20 |
|---|---|
| [Node.js] JWT에 대해 알아보기 (0) | 2024.01.18 |
| [Node.js] SNS 만들기 - Model 만들기 (0) | 2024.01.18 |
| [Node.js] SNS 만들기 - 라우터 만들기 (1) | 2024.01.13 |
| [Node.js] SNS 만들기 - Express로 서버 구축 (4) | 2024.01.13 |