첫 프로젝트 "도서 정보 사이트 구축 프로젝트"에서 SQL을 사용했다.
하지만, 조금 더 효율적이고 유지보수하기 쉬운 코드를 작성하기 위해 Sequelize ORM를 사용해보기로 했다.
Sequelize ORM를 사용한 이유는 아래와 같다.
근데 직접 테이블을 모델로 정의하는 과정이 오히려 나는 조금 더 복잡하고 익숙하지 않아 어려웠다.
이 부분은 Sequelize CLI을 통해서 모델과 마이그레이션 파일을 자동으로 생성하면 작업이 간소화되어 좋을 것 같다.
작은 변경 사항이라도 마이그레이션을 해야하는 점에서 더 번거로운 게 아닌 가 하는 생각이 들었지만, 협업에 있어서는 좋은 도구라 생각했다.
타입스크립트를 사용하려했으나, 모델 형성하는데 시퀄라이즈가 가지고 와지지 않는 오류가 발생하여 이유를 찾던 중 시퀄라이즈는 타입스크립트를 잘 지원해주지 않는다는 답을 얻을 수 있었다.
require('dotenv').config();
const env = process.env;
module.exports = {
development: {
username: env.DB_USER,
password: env.DB_PASSWORD,
database: env.DB_NAME,
host: env.DB_HOST,
dialect: 'mysql',
timezone: 'Asia/Seoul',
},
test: {
username: env.DB_USER,
password: null,
database: 'database_test',
host: env.DB_HOST,
dialect: 'mysql',
timezone: 'Asia/Seoul',
},
production: {
username: env.DB_USER,
password: null,
database: 'database_production',
host: env.DB_HOST,
dialect: 'mysql',
timezone: 'Asia/Seoul',
},
};
'use strict';
const { Sequelize, DataTypes } = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config')[env];
const db = {};
const sequelize = new Sequelize(config.database, config.username, config.password, config);
db.User = require('./User.js')(sequelize, DataTypes);
db.sequelize = sequelize;
module.exports = db;
'use strict';
const { Sequelize, DataTypes } = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config')[env];
config/config
파일에서 불러오기const db = {};
const sequelize = new Sequelize(config.database, config.username, config.password, config);
db
객체는 나중에 모든 모델을 포함할 객체sequelize
인스턴스를 초기화sequelize
인스턴스를 초기화 왜 하는 걸까?: DB와의 연결을 설정하기 위함
db.User = require('./User.js')(sequelize, DataTypes);
db
객체에 추가User.js
)에서 모델을 정의하는 함수가 반환, sequelize
인스턴스와 DataTypes
를 인자로 받음'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
'User',
{
user_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
autoIncrement: true,
primaryKey: true,
},
username: {
type: DataTypes.STRING(35),
allowNull: false
},
email: {
type: DataTypes.STRING(50),
allowNull: false
},
password: {
type: DataTypes.STRING(20),
allowNull: false,
},
address: DataTypes.STRING(50),
contact: DataTypes.STRING(50),
salt: {
type: DataTypes.STRING,
},
},
{
timestamps: false,
}
);
return User;
};
[User 모델 정의]
sequelize.define('User', {...},{...})
const {
signup,
signin,
reqResetPassword,
resetPassword,
getUserInfo,
updateUserInfo,
deleteUserAccount,
} = require('../controllers/UserController.js');
const router = require('express').Router();
router.post('/signup', signup);
router.post('/signin', signin);
router.post('/reset-password/request', reqResetPassword);
router.post('/reset-password', resetPassword);
router.get('/profile', getUserInfo).put('/profile', updateUserInfo);
router.delete('/account', deleteUserAccount);
module.exports = router;
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const db = require('../models');
const { validateToken } = require('../utils/auth');
const User = db.User;
const userService = {
createUser: async (user) => {
const salt = crypto.randomBytes(10).toString('base64');
const hashPwd = userService.hashPwd(user.password, salt);
return await User.create({ ...user, password: hashPwd, salt: salt });
},
getDecodedUser: async (req, res) => {
const decodedPayload = await validateToken(req);
if (decodedPayload instanceof jwt.TokenExpiredError) {
return res.status(StatusCodes.UNAUTHORIZED).json({ msg: '토큰이 만료됐습니다.' });
} else if (decodedPayload instanceof jwt.JsonWebTokenError) {
return res.status(StatusCodes.UNAUTHORIZED).json({ msg: '토큰이 잘못됐습니다' });
}
return decodedPayload;
},
updateUser: async (updatedUser, email) => {
const [numOfAffectedRows, affectedRows] = await User.update(updatedUser, { where: { email } });
return affectedRows;
},
deleteUser: async (email) => {
return await User.destroy({ where: { email } });
},
findUserByEmail: async (email) => {
return await User.findOne({ where: { email } });
},
generateToken: (id, email, name) => {
return jwt.sign({ id, email, name }, process.env.PRIVATE_KEY, {
expiresIn: '1h',
issuer: 'juhee',
});
},
hashPwd: (password, salt) => {
return crypto.pbkdf2Sync(password, salt, 10000, 10, 'sha512').toString('base64');
},
resetPassword: async (email, password, salt) => {
const hashPwd = userService.hashPwd(password, salt);
return await User.update({ password: hashPwd }, { where: { email } });
},
};
module.exports = userService;
const userService = require('../services/userService');
const { StatusCodes } = require('http-status-codes');
const signup = async (req, res) => {
try {
const user = req.body;
await userService.createUser(user);
res.status(StatusCodes.CREATED).json({ status: 201 });
} catch (err) {
return res.status(StatusCodes.BAD_REQUEST).json({ message: '회원가입 중에 오류가 발생했습니다.' });
}
};
const signin = async (req, res) => {
try {
const { email, password } = req.body;
const user = await userService.findUserByEmail(email);
if (!user) {
return res.status(StatusCodes.UNAUTHORIZED).json({ status: 401 });
}
const hashPwd = userService.hashPwd(password, user.salt);
if (user.password !== hashPwd) {
return res.status(StatusCodes.UNAUTHORIZED).json({ status: 401 });
}
const token = userService.generateToken(user.id, user.email, user.password);
res.cookie('token', token, { httpOnly: true });
res.status(StatusCodes.OK).json({ status: 200, user: user });
} catch (err) {
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ message: '로그인 중에 오류가 발생했습니다.' });
}
};
const reqResetPassword = async (req, res) => {
try {
const { email } = req.body;
const user = await userService.findUserByEmail(email);
if (user) {
return res
.status(StatusCodes.OK)
.json({ email: user.email, msg: '비밀번호 초기화가 요청됐습니다.' });
}
res.status(StatusCodes.UNAUTHORIZED).json({ msg: '비밀번호 초기화 요청에 실패 했습니다.' });
} catch (err) {
res
.status(StatusCodes.INTERNAL_SERVER_ERROR)
.json({ msg: '비밀번호 초기화 요청 중에 문제가 발생했습니다.' });
}
};
const resetPassword = async (req, res) => {
try {
const { email, password } = req.body;
const user = await userService.findUserByEmail(email);
if (!user) {
return res.status(StatusCodes.NOT_FOUND).json({ status: 404, msg: '회원이 존재하지 않습니다.' });
}
await userService.resetPassword(email, password, user.salt);
res.status(StatusCodes.OK).json({ msg: '비밀번호가 초기화됐습니다.' });
} catch (err) {
res
.status(StatusCodes.INTERNAL_SERVER_ERROR)
.json({ msg: '비밀번호 초기화 중에 문제가 발생했습니다.' });
}
};
const getUserInfo = async (req, res) => {
try {
const decodedPayload = await userService.getDecodedUser(req, res);
const user = userService.findUserByEmail(decodedPayload.email);
if (!user) {
return res.status(StatusCodes.NOT_FOUND).json({ status: 404, msg: '회원이 존재하지 않습니다.' });
}
res.status(StatusCodes.OK).json({ status: 200, user: user });
} catch (err) {
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ msg: '회원 조회 중에 문제가 발생했습니다.' });
}
};
const updateUserInfo = async (req, res) => {
try {
const updatedUser = req.body;
const decodedPayload = await userService.getDecodedUser(req, res);
const user = userService.findUserByEmail(decodedPayload.email);
if (!user) {
return res.status(StatusCodes.NOT_FOUND).json({ status: 404, msg: '회원이 존재하지 않습니다.' });
}
await userService.updateUser(updatedUser, decodedPayload.email);
res.status(StatusCodes.OK).json({ status: 200, msg: '회원정보가 수정되었습니다' });
} catch (err) {
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ msg: '회원 수정 중에 문제가 발생했습니다.' });
}
};
const deleteUserAccount = async (req, res) => {
try {
const decodedPayload = await userService.getDecodedUser(req, res);
const user = userService.findUserByEmail(decodedPayload.email);
if (!user) {
return res.status(StatusCodes.NOT_FOUND).json({ status: 404, msg: '회원이 존재하지 않습니다.' });
}
await userService.deleteUser(decodedPayload.email);
res.status(StatusCodes.OK).json({ status: 200, msg: '회원이 삭제되었습니다.' });
} catch (err) {
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ msg: '회원 삭제 중에 문제가 발생했습니다.' });
}
};
module.exports = {
signup,
signin,
reqResetPassword,
resetPassword,
getUserInfo,
updateUserInfo,
deleteUserAccount,
};
sql을 작성하지 않아도 되는 부분과, 미리 정의된 관계를 활용할 수 있는 것은 Sequelize ORM의 가장 큰 장점이고 편의하다는 생각이 들었다.
아직까진 sql이 필요한 부분도 있어서 익숙해지는 데는 시간이 필요하지만, 오로지 sql만으로 사용하는 부분에 있어 Sequelize ORM은 그런 단점들을 보완해주는 데 정말 좋은 이점이라고 생각한다.
여전히 모델을 정의와 관계를 매핑하는 데 있어 어려움이 있지만 잘 사용할 수 있다면 여러 작업을 하는데 훨씬 빠르고 간편하게 사용할 수 있을 것 같다.