//routers / index.js
const express = require("express");
const router = express.Router();
//localhost:3000/users/ ---
const userRouter = require("./userRouter");
router.use("/users", userRouter.router);
module.exports = router;
//routers / userRouter.js
const express = require("express");
const router = express.Router();
const userController = require("../controller/userController");
router.post("/signup", userController.signUp);
router.post("/login", userController.login);
module.exports = {router};
front와 연결할때 필요한 uri를 위해 router 작성
회원가입 : /users/signup
로그인 : /users/login
기능별로 나누기 위해 공통적인 부분은 index.js의 users로 지정하고 나머지 signup / login을 지정했다.
//controllers / userController
const userService = require("../services/userService");
const etc = require("../middleware/etc");
const signUp = async (req, res) => {
try {
const { nickname, email, password } = req.body;
if (!nickname || !email || !password) {
return res
.status(400)
.json({ message: "필수 항목을 꼭 기입해야 합니다." });
}
const result = await userService.signUp(nickname, email, password);
return res.status(200).json({ message: "sign-up successfully" , result });
} catch (error) {
return res.status(error.statusCode || 500).json({ message: error.message });
}
};
const login = async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res
.status(400)
.json({ message: "이메일 주소와 패스워드를 확인해 주세요." });
}
// 이메일 체크
const userCheck = await userService.login(email);
if (!userCheck) {
throw new Error(401, "not found email");
}
//비밀번호 체크
const passwordCheck = userCheck[0].password;
const result = await etc.checkHash(password, passwordCheck);
//결과가 나오면 토큰 생성
if (result) {
const userId = userCheck[0].id;
const userNickname = userCheck[0].nickname;
const jwtToken = etc.generateToken(email , userId);
if (!jwtToken) {
throw new Error(401, "token generation failed");
}
return res.status(200).json({
message: "login success",
token: `${jwtToken}`,
id: userId,
nickname: userNickname,
});
}
} catch (error) {
return res.status(error.statusCode || 500).json({ message: error.message });
}
};
module.exports = {
signUp,
login,
};
회원가입 : front에서 건네받은 닉네임과 이메일 , 패스워드를 한번 확인하고 값이 정상적으로 들어오지 않으면 필수 항목이라 꼭 기입해야 한다는 message를 내보낸다. 그 후 Service 부분에 signUp 함수 사용
로그인 : 회원가입을 마치고 DB에 회원정보가 저장된 후 로그인을 할때 회원가입과 마찬가지 값을 정상적으로 입력하지 않고 로그인 시도를 하면 이메일과 패스워드를 확인하라는 메세지 출력. 그 후 Service 부분에 login 함수 사용
//middlewares / etc.js
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const secretKey = process.env.SECRET_KEY;
//hash 만들기
const makeHash = async (password, saltRounds) => {
return bcrypt.hash(password, saltRounds);
};
//hash 검증
const checkHash = async (password, hashedPassword) => {
return bcrypt.compare(password, hashedPassword);
};
//이메일 형식 정규식
const validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
//패스워드 정규식
const validatePassword = (password) => {
const passwordRegex = /^.{10,}$/;
return passwordRegex.test(password);
};
//token 만들기
const generateToken = (email, userId) => {
return jwt.sign({ email, userId }, secretKey);
};
//디코딩 token
const decoded = (token, secretKey) => {
const result = jwt.verify(token, secretKey);
return result;
};
/* 암호화된 패스워드와 발행받는 토큰의 형태는 이러한 형태를 띄고있다.
회원가입 : 암호화된 패스워드 ($2b$10$GmbKm5JIU2czUm0c6YCjR.ar64FVuZP0Je3BI/Cm.ONIQcCZYC1Q.)
로그인 : 토근 발행 ("token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVvaXNvYXVuZGFrc0B3ZWNvZGUuY28ua3IiLCJ1c2VySWQiOjM3LCJpYXQiOjE2OTc3MDcyNTZ9.UhqosTgsGtIaZpl375ZfWPM7mqihX4kZMh7lnxH6Dcg")*/
패스워드 암호화 및 로그인 후 다른 항목을 엑세스를 위한 Token 발행과 검증을 하는 부분
//Services / userService.js
const userDao = require("../models/userDao");
const etc = require("../middleware/etc");
const signUp = async (nickname, email, password) => {
//패스워드 예외처리
if (!etc.validatePassword(password)) {
const err = new Error("패스워드는 10자리 이상이어야 합니다.");
err.status = 409;
throw err;
}
//이메일 예외처리
if (!etc.validateEmail(email)) {
const err = new Error("이메일 형식이 올바르지 않습니다.");
err.status = 409;
throw err;
}
//중복 이메일 예외처리
const checkEmail = await userDao.checkEmail(email);
if (checkEmail) {
const err = new Error("이미 사용 중인 이메일입니다.");
err.status = 409;
throw err;
}
const hashedPassword = await etc.makeHash(password, 10);
const insertHash = await userDao.signUp(nickname, email, hashedPassword);
const result = await etc.checkHash(password, hashedPassword);
return insertHash;
};
const login = async (email) => {
const users = await userDao.login(email);
return users;
};
module.exports = {
signUp,
login,
};
Controller에서 받은 인자값을 DB에 접근하기 전에 예외처리 및 검증하는 Service 부분
//Models / userDao.js
const { DataSource } = require("typeorm");
const appDataSource = new DataSource({
type: process.env.TYPEORM_CONNECTION,
host: process.env.TYPEORM_HOST,
port: process.env.TYPEORM_PORT,
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
database: process.env.TYPEORM_DATABASE,
});
try {
appDataSource.initialize().then(() => {
console.log("USER Data Source has been initialized");
});
} catch (err) {
console.log(err);
}
const signUp = async (nickname, email, hashedpassword) => {
try {
const result = await appDataSource.query(
`
insert into users (nickname , email , password) values (?,?,?)
`,
[nickname, email, hashedpassword]
);
return result;
} catch (error) {
const err = new Error("Data insert error");
err.status = 500;
throw err;
}
};
const checkEmail = async (email) => {
try {
const result = await appDataSource.query(
`
select email from users where email = ?
`,
[email]
);
return result.length > 0;
} catch (error) {
const err = new Error("Data read error");
err.status = 500;
throw err;
}
};
const login = async (email) => {
try {
const result = await appDataSource.query(
`
select * from users where email = ?
`,
[email]
);
return result;
} catch (error) {
const err = new Error("Data read error");
err.status = 500;
throw err;
}
};
module.exports = {
signUp,
login,
checkEmail,
};
DB에 직접적으로 연결하는 부분이며 Service 부분에서 인자로 받은 값들을 DB에 저장 및 select 문으로 확인하고 이메일 중복이 있는지 확인하고 그 값을 return하는 역할
로그인 부분은 인자로 받은 이메일이 회원정보가 담겨있는 users라는 테이블에 저장이 되어있는지 판단 후 다시 Service 부분으로 return 하는 역할
이렇게 회원가입과 로그인을 진행 할 수 있게 된다.
회원가입과 로그인 기능구현이 시작하기전에는 간단하다고 생각했지만 예외처리와 기능별로 pattern을 나누는것이 쉽지 않았다. 다음에는 게시물 , 댓글 CRUD 기능을 작성해보려한다.