sequelize 이용하여 db, table생성하기
1. 설계하기 - ERD
2. 필요한 라이브러리들, 파일, 폴더를 생성 후 config.json설정
# 라이브러리를 설치합니다.
npm install express sequelize mysql2 cookie-parser jsonwebtoken
# sequelize-cli, nodemon 라이브러리를 DevDependency로 설치합니다.
npm install -D sequelize-cli nodemon
# 설치한 sequelize를 초기화 하여, sequelize를 사용할 수 있는 구조를 생성합니다.
npx sequelize init
이걸 해야 config, migrations, models등등이 생김
#nodemon 이용하기
npx nodemon app.js
aws RDS엔드포인트: express-database.cv64ek1xh9gd.ap-northeast-2.rds.amazonaws.com / 포트: 3306
./config/config.json
{
"development": {
"username": "root",
"password": "4321aaaa",
"database": "sequelize_relation",
"host": "",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": null,
"database": "database_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
# Users 모델
npx sequelize model:generate --name Users --attributes email:string,password:string
# UserInfos 모델
npx sequelize model:generate --name UserInfos --attributes UserId:integer,name:string,age:integer,gender:string,profileImage:string
# Posts 모델
npx sequelize model:generate --name Posts --attributes title:string,content:string
# Comments 모델
npx sequelize model:generate --name Comments --attributes UserId:integer,PostId:integer,comment:string
3. migrations 파일들에 코드를 작성한 후에 아래의 코드로 DB를 생성하고 테이블을 만든다.
# config/config.json에 설정된 DB를 생성합니다.
npx sequelize db:create
{
"development": {
"username": "root",
"password": "4321aaaa",
"database": "sequelize_assignment",
"host": "express-database.cv64ek1xh9gd.ap-northeast-2.rds.amazonaws.com",
"dialect": "mysql"
},
즉 "database"란은 내가 DB를 저장할 저장소의 이름.
# 해당 프로젝트에 Migrations에 정의된 Posts 테이블을 MySQL에 생성합니다.
npx sequelize db:migrate
DB와 테이블들이 정상적으로 설치되었는지 New Query를 열어 desc로 확인해본다.
4. 이후 models file에 코드를 작성한다. 여기서 models는 테이블 그 자체라고 생각하면 될 듯.
즉 테이블안의 컬럼 값의 속성을 정해주고, 테이블들 간의 관계성을 설정해주는 것.
ex) models/users.js - 아래처럼 UserInfos 테이블과 1:1 관계를 설정해준다.
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Users extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
// 1. Users 모델에서
this.hasOne(models.UserInfos, { // 2. UserInfos 모델에게 1:1 관계 설정을 합니다. //this 는 현제 테이블, 즉 User 모델
sourceKey: 'userId', // 3. Users 테이블의 userId 컬럼을
foreignKey: 'UserId', // 4. UserInfos 모델의 UserId 컬럼과 연결합니다.
});
}
}
Users.init(
{
userId: {
allowNull: false, // NOT NULL
autoIncrement: true, // AUTO_INCREMENT
primaryKey: true, // Primary Key (기본키)
type: DataTypes.INTEGER,
},
email: {
allowNull: false, // NOT NULL
type: DataTypes.STRING,
unique: true,
},
password: {
allowNull: false, // NOT NULL
type: DataTypes.STRING,
},
createdAt: {
allowNull: false, // NOT NULL
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
updatedAt: {
allowNull: false, // NOT NULL
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
},
{
sequelize,
modelName: 'Users',
}
);
return Users;
};
models/userinfos.js
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class UserInfos extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
// 1. UserInfos 모델에서
this.belongsTo(models.Users, {
// 2. Users 모델에게 1:1 관계 설정을 합니다.
targetKey: 'userId', // 연결될 컬럼을 입력
foreignKey: 'UserId', // 4. UserInfos 모델의 UserId 컬럼과 연결합니다.
});
}
}
UserInfos.init(
{
userInfoId: {
allowNull: false, // NOT NULL
autoIncrement: true, // AUTO_INCREMENT
primaryKey: true, // Primary Key (기본키)
type: DataTypes.INTEGER,
},
UserId: {
allowNull: false, // NOT NULL
type: DataTypes.INTEGER,
unique: true, // UNIQUE
},
name: {
allowNull: false, // NOT NULL
type: DataTypes.STRING,
},
age: {
allowNull: false, // NOT NULL
type: DataTypes.INTEGER,
},
gender: {
allowNull: false, // NOT NULL
type: DataTypes.STRING,
},
profileImage: {
type: DataTypes.STRING,
},
createdAt: {
allowNull: false, // NOT NULL
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
updatedAt: {
allowNull: false, // NOT NULL
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
},
{
sequelize,
modelName: 'UserInfos',
}
);
return UserInfos;
};
Users모델은 posts와도 연결되기 때문에 1:N관계를 추가해준다.
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Users extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
// 1. Users 모델에서
this.hasOne(models.UserInfos, {
// 2. UserInfos 모델에게 1:1 관계 설정을 합니다. //this 는 현제 테이블, 즉 User 모델
sourceKey: 'userId', // 3. Users 테이블의 userId 컬럼을
foreignKey: 'UserId', // 4. UserInfos 모델의 UserId 컬럼과 연결합니다.
});
// 1. Users 모델에서
this.hasMany(models.Posts, {
// 2. Posts 모델에게 1:N 관계 설정을 합니다.
sourceKey: 'userId', // 3. Users 모델의 userId 컬럼을
foreignKey: 'UserId', // 4. Posts 모델의 UserId 컬럼과 연결합니다.
});
}
}
Users.init(
{
userId: {
allowNull: false, // NOT NULL
autoIncrement: true, // AUTO_INCREMENT
primaryKey: true, // Primary Key (기본키)
type: DataTypes.INTEGER,
},
email: {
allowNull: false, // NOT NULL
type: DataTypes.STRING,
unique: true,
},
password: {
allowNull: false, // NOT NULL
type: DataTypes.STRING,
},
createdAt: {
allowNull: false, // NOT NULL
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
updatedAt: {
allowNull: false, // NOT NULL
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
},
{
sequelize,
modelName: 'Users',
}
);
return Users;
};
models/posts.js 를 Users와 N:1의 관계를 설정해준다.
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Posts extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
// 1. Posts 모델에서
this.belongsTo(models.Users, {
// 2. Users 모델에게 N:1 관계 설정을 합니다.
targetKey: 'userId', // 3. Users 모델의 userId 컬럼을
foreignKey: 'UserId', // 4. Posts 모델의 UserId 컬럼과 연결합니다.
});
}
}
Posts.init(
{
postId: {
allowNull: false, // NOT NULL
autoIncrement: true, // AUTO_INCREMENT
primaryKey: true, // Primary Key (기본키)
type: DataTypes.INTEGER,
},
UserId: {
allowNull: false, // NOT NULL
type: DataTypes.INTEGER,
},
title: {
allowNull: false, // NOT NULL
type: DataTypes.STRING,
},
content: {
allowNull: false, // NOT NULL
type: DataTypes.STRING,
},
createdAt: {
allowNull: false, // NOT NULL
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
updatedAt: {
allowNull: false, // NOT NULL
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
},
{
sequelize,
modelName: 'Posts',
}
);
return Posts;
};
**DB를 삭제하는 명령어 npx sequelize db:drop
**DB를 생성하는 명령어 npx sequelize db:create
5. DB, migrations, models(tables)을 생성하고 연결관계를 만들어 준 후에 app.js, routes들을 설정한다
// app.js
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
const PORT = 3018;
app.use(express.json());
app.use(cookieParser());
app.use('/api', []);
app.listen(PORT, () => {
console.log(PORT, '포트 번호로 서버가 실행되었습니다.');
});
email, password은 body 형태로 Users 테이블에 들어갈 것이고,
name, age, gender, profileImage는 body 형태로 UserInfos 테이블에 들어갈 것이다.
어떤 정보가 어디로 들어갈지 생각을 한 후에 router를 구성해준다!
userInfo는 user의 id값을 기반으로 만들어진다는 점 인지.
// routes/users.route.js
const express = require('express');
const { Users, UserInfos } = require('../models');
const jwt = require('jsonwebtoken');
const router = express.Router();
// 회원가입
router.post('/users', async (req, res) => {
try {
const {
email,
password,
confirmPassword,
name,
age,
gender,
profileImage,
} = req.body;
//동일한 가입자가 있는지 email을 통해 확인한다.
const isExistUser = await Users.findOne({ where: { email } });
if (isExistUser) {
return res.status(409).json({ message: '이미 존재하는 이메일입니다.' });
}
//닉네임 길이 제한
if (name.length < 3) {
res
.status(412)
.json({ errorMessage: '닉네임 형식이 일치하지 않습니다.' });
return;
}
//닉네임 형식
const nameRegex = /^[a-zA-Z0-9]+$/;
if (!nameRegex.test(name)) {
res
.status(412)
.json({ errorMessage: '닉네임 형식이 일치하지 않습니다.' });
return;
}
//닉네임 4자리 이상, 닉네임과 같은 값 포함
if (password.length < 4 || password.includes(name)) {
res
.status(412)
.json({ errorMessage: '패스워드에 닉네임이 포함되어있습니다.' });
return;
}
//비번 재확인
if (password !== confirmPassword) {
res.status(412).json({
errorMessage: '확인용 패스워드가 일치하지 않습니다.',
});
return;
}
//Users 테이블에 사용자를 추가합니다.
const user = await Users.create({ email, password });
// UserInfos 테이블에 사용자 정보를 추가합니다.
const userInfo = await UserInfos.create({
UserId: user.userId, // 생성한 유저의 userId를 바탕으로 사용자 정보를 생성합니다.
name,
age,
gender: gender.toUpperCase(), // 성별을 대문자로 변환합니다.
profileImage,
});
return res.status(201).json({ message: '회원가입이 완료되었습니다.' });
} catch (error) {
console.error(error);
return res.status(400).json({ errorMessage: '회원가입에 실패하였습니다.' });
}
});
// 로그인
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
//사용자가 존재하는지 찾아보자
const user = await Users.findOne({ where: { email } });
if (!user) {
return res
.status(401)
.json({ message: '닉네임 또는 패스워드를 확인해주세요.' });
} else if (user.password !== password) {
return res.status(401).json({ message: '비밀번호가 일치하지 않습니다.' });
}
//jwt를 생성하고
const token = jwt.sign({ userId: user.userId }, 'customized_secret_key'); //user안에 있는 userId,
//쿠키를 발급
res.cookie('authorization', `Bearer ${token}`);
//response할당
return res.status(200).json({ message: '로그인 성공' });
} catch (error) {
console.error(error);
return res.status(400).json({ message: '서버 에러' });
}
});
// 사용자 조회
router.get('/users/:userId', async (req, res) => {
const { userId } = req.params;
const user = await Users.findOne({
where: { userId },
attributes: ['userId', 'email', 'createdAt', 'updatedAt'],
include: [
{
model: UserInfos, // 1:1 관계를 맺고있는 UserInfos 테이블을 조회합니다.
attributes: ['name', 'age', 'gender', 'profileImage'],
},
],
});
return res.status(200).json({ data: user });
});
module.exports = router;