[SeSACx코딩온] Sequelize
Sequelize를 이용한 모델 및 관계 설정
- Sequelize는 Node.js 애플리케이션에서 데이터베이스와의 상호작용을 간편하게 처리할 수 있도록 도와주는 ORM(Object-Relational Mapping) 라이브러리입니다.
- ORM을 사용하게 되면 SQL문을 쓰지 않고도 CRUD를 할 수 있다는 장점이 있습니다.
프로젝트 디렉토리 구조
project/
│
├── sequelize/
│ ├── models/
│ │ ├── index.js
│ │ ├── Profile.js
│ │ ├── Player.js
│ │ └── Team.js
│ │
│ ├── routes/
│ │ └── index.js
│ │
│ ├── controller/
│ │ └── Cmain.js
│ │
│ └── views/
│ └── index.ejs
│
├── config/
│ └── config.json
│
├── node_modules/
│ └── [npm packages]
│
├── index.js
├── package.json
└── package-lock.json
설치할 npm 목록
npm install ejs express mysql2 sequelize sequelize-cli
sequelize/package.json
{
"name": "17-sequelize",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"ejs": "^3.1.10",
"express": "^4.19.2",
"mysql2": "^3.10.1",
"sequelize": "^6.37.3",
"sequelize-cli": "^6.6.2"
}
}
1. Sequelize 기본 설정
1) 기본 Express 서버 설정
sequelize/index.js
const express = require('express'); // Express 웹 프레임워크 불러오기
const app = express(); // Express 애플리케이션 생성
const PORT = 8000; // 서버가 사용할 포트 번호
const router = require('./routes/index'); // 라우터 모듈 불러오기
const { sequelize } = require('./models'); // Sequelize 인스턴스와 모델을 불러오기
app.set('view engine', 'ejs'); // EJS를 템플릿 엔진으로 설정
app.set('views', './views'); // EJS 템플릿 파일이 위치할 디렉토리 설정
app.use(express.urlencoded({ extended: true })); // URL-encoded 데이터 파싱 미들웨어
app.use(express.json()); // JSON 데이터 파싱 미들웨어
app.use('/', router); // 루트 경로에 대한 요청을 라우터로 전달
// 데이터베이스와 모델을 동기화하고 서버를 시작
sequelize
// force : true ; 서버 실행할 때마다 테이블 재생성
// force : false ; 서버 실행 시 테이블이 없으면 생성
.sync({ force: true }) // 데이터베이스와 모델 간의 동기화. 테이블을 강제로 재생성
.then(() => {
app.listen(PORT, () => console.log(`${PORT}에서 서버 실행 중`));
console.log('데이터베이스 연결 성공');
})
.catch((err) => {
console.error('데이터베이스 연결 실패:', err); // 데이터베이스 연결 에러 출력
});
- 기본적인 Express 서버와 Sequelize 초기화 설정을 포함합니다.
express모듈을 사용하여 웹 서버를 설정하고,sequelize.sync({ force: true })를 통해 데이터베이스와 모델 간의 동기화를 수행합니다. sequelize.sync({ force: true })은 개발 중 데이터베이스 스키마를 변경할 때 유용하지만 모든 데이터가 삭제되므로 신중하게 사용해야 합니다.- 초기 설정 시
sequelize.sync({ force: true })를 사용하고 DB에 데이터가 생기면{ force: false }로 변경해 줍니다. - 서버가 성공적으로 실행되면 지정된 포트에서 요청을 수신하고, 데이터베이스와의 연결 상태를 콘솔에 출력합니다.
2) DB 관련 세부설정
sequelize/config/config.json
{
"development": {
"username": "user", // 데이터베이스 사용자 이름
"password": "12345678", // 데이터베이스 사용자 비밀번호
"database": "codingon", // 사용할 데이터베이스 이름
"host": "127.0.0.1", // 데이터베이스 서버 호스트 주소(거의 고정)
"dialect": "mysql" // 사용할 데이터베이스 종류 (배포 시 해당 내용이 빠지면 에러 발생함)
},
"test": {
// 테스트 환경 설정 (추후 필요 시 작성)
},
"production": {
// 프로덕션 환경 설정 (추후 필요 시 작성)
}
}
2. 데이터베이스 모델 정의
1) Player 모델 정의
sequelize/models/Player.js
const playerModel = (sequelize, DataTypes) => {
// sequelize 매개변수 : model/index.js에서의 sequelize (db 연결 정보를 입력하여 생성한 객체)
// DataTypes 매개변수 : model/index.js에서의 sequelize (sequelize 패키지 자체)
const Player = sequelize.define(
// param 1 : 모델 이름 설정
'Player', {
// param 2 : 모델 컬럼 정의
player_id: {
type: DataTypes.INTEGER, // 데이터 타입은 INTEGER
primaryKey: true, // 기본키로 설정
allowNull: false, // NULL 값 허용 안함
autoIncrement: true // 자동 증가 설정
},
name: {
type: DataTypes.STRING(63), // 데이터 타입은 문자열, 최대 길이 63자
allowNull: false // NULL 값 허용 안함
},
age: {
type: DataTypes.INTEGER, // 데이터 타입은 INTEGER
allowNull: false // NULL 값 허용 안함
}
},
// param 3 : 모델 옵션 정의
{
freezeTableName: true // 모델 이름을 테이블 이름으로 사용(테이블 이름을 복수형으로 변환하지 않음)
timestamps: false // 데이터가 추가되고 수정된 시간을 자동으로 컬럼을 만들어서 기록
});
return Player;
}
module.exports = playerModel;
Player모델은player_id,name,age의 세 가지 속성을 가지며, 각각의 속성에 대한 데이터 타입과 제약 조건을 정의합니다.player_id는 기본키로 설정되어 있으며 자동 증가합니다.name과age는 각각 문자열과 정수형 데이터로 정의되고 NULL 값을 허용하지 않습니다.freezeTableName: true옵션을 사용하여 모델 이름과 테이블 이름을 동일하게 유지합니다.freezeTableName: false를 사용하면 테이블의 이름을 자동으로 명명합니다. 이때 복수형으로 바뀝니다.
2) Profile 모델 정의
sequelize/models/Profile.js
const ProfileModel = (sequelize, DataTypes) => {
const Profile = sequelize.define('Profile', {
profile_id: {
type: DataTypes.INTEGER, // 데이터 타입은 INTEGER
primaryKey: true, // 기본키로 설정
allowNull: false, // NULL 값 허용 안함
autoIncrement: true // 자동 증가 설정
},
position: {
type: DataTypes.STRING(63), // 데이터 타입은 문자열, 최대 길이 63자
allowNull: false // NULL 값 허용 안함
},
salary: {
type: DataTypes.INTEGER, // 데이터 타입은 INTEGER
allowNull: false // NULL 값 허용 안함
}
}, {
freezeTableName: true // 모델 이름을 테이블 이름으로 사용(테이블 이름을 복수형으로 변환하지 않음)
});
return Profile;
};
module.exports = ProfileModel;
Profile모델은profile_id,position,salary의 세 가지 속성을 가지며, 각각의 속성에 대한 데이터 타입과 제약 조건을 정의합니다.profile_id는 기본키로 설정되어 있으며 자동 증가합니다.position과salary는 각각 문자열과 정수형 데이터로 정의되고 NULL 값을 허용하지 않습니다.freezeTableName: true옵션을 사용하여 모델 이름과 테이블 이름을 동일하게 유지합니다.
3) Team 모델 정의
sequelize/models/Team.js
const TeamModel = (sequelize, DataTypes) => {
const Team = sequelize.define('Team', {
team_id: {
type: DataTypes.INTEGER, // 데이터 타입은 INTEGER
primaryKey: true, // 기본키로 설정
allowNull: false, // NULL 값 허용 안함
autoIncrement: true // 자동 증가 설정
},
name: {
type: DataTypes.STRING(63), // 데이터 타입은 문자열, 최대 길이 63자
allowNull: false // NULL 값 허용 안함
}
}, {
freezeTableName: true // 모델 이름을 테이블 이름으로 사용(테이블 이름을 복수형으로 변환하지 않음)
});
return Team;
};
module.exports = TeamModel;
Team모델은team_id와name의 두 가지 속성을 가지며, 각각의 속성에 대한 데이터 타입과 제약 조건을 정의합니다.team_id는 기본키로 설정되어 있으며 자동 증가합니다.name은 최대 63자의 문자열로 정의되고 NULL 값을 허용하지 않습니다.freezeTableName: true옵션을 사용하여 모델 이름과 테이블 이름을 동일하게 유지합니다.
3. 모델 간의 관계 설정
모델 간의 관계를 정의하여 데이터베이스의 외래 키 및 참조 관계를 설정합니다. 이를 통해 데이터베이스의 무결성을 유지하고 모델 간의 연관성을 관리할 수 있습니다.
1) 모델 간의 관계 정의
sequelize/models/index.js
const Sequelize = require('sequelize'); // Sequelize 모듈 불러오기
const config = require(__dirname + '/../config/config.json')["development"]; // 데이터베이스 설정 불러오기 (config.json으로 가서 development의 키에 해당하는 값을 가져온다는 의미)
const db = {}; // 데이터베이스 객체를 저장할 빈 객체
const sequelize = new Sequelize(config.database, config.username, config.password, config); // Sequelize 인스턴스 생성
// 모델 불러오기
const PlayerModel = require('./Player')(sequelize, Sequelize);
const ProfileModel = require('./Profile')(sequelize, Sequelize);
const TeamModel = require('./Team')(sequelize, Sequelize);
// 모델 간의 관계 설정
// 1) Player : Profile = 1 : 1
PlayerModel.hasOne(ProfileModel, {
onDelete: 'CASCADE', // 플레이어와 연관된 프로필도 삭제
onUpdate: 'CASCADE', // 플레이어와 연관된 프로필도 업데이트
foreignKey: 'player_id', // ProfileModel에 player_id 외래 키 생성
sourceKey: 'player_id' // PlayerModel의 player_id 컬럼 참조
});
ProfileModel.belongsTo(PlayerModel, {
foreignKey: 'player_id', // ProfileModel에 player_id 외래 키 생성
targetKey: 'player_id' // 참조할 PlayerModel의 키는 player_id
});
// 2) Team : Player = 1 : N
TeamModel.hasMany(PlayerModel, {
foreignKey: 'team_id', // PlayerModel에 team_id 외래 키 생성
sourceKey: 'team_id' // TeamModel의 team_id 컬럼 참조
});
PlayerModel.belongsTo(TeamModel, {
foreignKey: 'team_id', // PlayerModel에 team_id 외래 키 생성
targetKey: 'team_id' // 참조할 TeamModel의 키는 team_id
});
db.sequelize = sequelize; // sequelize 인스턴스를 db 객체에 추가
db.Sequelize = Sequelize; // Sequelize 모듈을 db 객체에 추가
db.Player = PlayerModel; // Player 모델을 db 객체에 추가
db.Profile = ProfileModel; // Profile 모델을 db 객체에 추가
db.Team = TeamModel; // Team 모델을 db 객체에 추가
module.exports = db; // db 객체 내보내기
- 이 코드는 모델 간의 관계를 설정합니다.
Player와Profile사이의 1:1 관계를 설정하고,Player가 하나의Profile을 가지며,Profile은 하나의Player에 속한다고 정의합니다. Team과Player사이의 1:N 관계를 설정하여 하나의Team은 여러 명의Player를 가질 수 있으며,Player는 하나의Team에 속한다고 정의합니다.onDelete와onUpdate옵션을 사용하여 관계가 변경될 때의 동작을 설정합니다.- sequelize는 Sequelize의 인스턴스이고, Sequelize는 Sequelize 라이브러리 자체를 의미합니다. 이들을 db 객체에 추가합니다. 또한 위에서 작성한 모델(데이터베이스 객체)를
db객체에 추가하고 내보냅니다.
참고 사항
- 맨처음 파일 설정하면 등록이 되어버리기때문에 중간에 파일명을 바꾸거나해도 등록되어있기에 계속 에러가 발생합니다.
- 파일을 삭제하고 새로 만들어야 해결됩니다. 그래도 해결되지 않을 땐 VScode를 종료 후 재실행 시 해결됩니다.
- 코드를 실행하고 MySQL 워크벤치에 테이블이 생성되는데 Mac은 테이블명이 대문자로, Windows는 소문자로 생성됩니다. MySQL은 대소문자를 구분하지 않으므로 상관은 없습니다.
2) 만약 N:M 관계가 있는 경우에는?
- Sequelize에서 N 대 M 관계를 설정하려면 중간 테이블을 사용해야 합니다. 중간 테이블은 두 모델 간의 다대다 관계를 연결해주는 역할을 합니다.
- 여기서 예시로
PlayerTeamModel이라는 중간 테이블을 생성해 보겠습니다.
(1) 중간 테이블 정의
PlayerTeam모델은player_id와team_id두 개의 외래 키를 가집니다.- 이 외래 키들은 각각
Player모델과Team모델을 참조합니다.
sequelize/models/PlayerTeam.js
const PlayerTeamModel = (sequelize, DataTypes) => {
const PlayerTeam = sequelize.define('PlayerTeam', {
player_id: {
type: DataTypes.INTEGER, // 데이터 타입은 INTEGER
allowNull: false, // NULL 값 허용 안함
references: {
model: 'Player', // 참조할 모델 이름
key: 'player_id' // 참조할 모델의 키
}
},
team_id: {
type: DataTypes.INTEGER, // 데이터 타입은 INTEGER
allowNull: false, // NULL 값 허용 안함
references: {
model: 'Team', // 참조할 모델 이름
key: 'team_id' // 참조할 모델의 키
}
}
}, {
freezeTableName: true // 모델 이름을 테이블 이름으로 사용
});
return PlayerTeam;
};
module.exports = PlayerTeamModel;
(2) 모델 간의 N 대 M 관계 정의
const PlayerTeamModel = require('./PlayerTeam')(sequelize, Sequelize);
// 3) Player : Team = N : M (다대다 관계 설정)
PlayerModel.belongsToMany(TeamModel, {
through: PlayerTeamModel, // 중간 테이블 설정
foreignKey: 'player_id', // PlayerTeamModel에 player_id fk 생성
otherKey: 'team_id' // PlayerTeamModel에 team_id fk 생성
});
TeamModel.belongsToMany(PlayerModel, {
through: PlayerTeamModel, // 중간 테이블 설정
foreignKey: 'team_id', // PlayerTeamModel에 team_id fk 생성
otherKey: 'player_id' // PlayerTeamModel에 player_id fk 생성
});
db.PlayerTeam = PlayerTeamModel;
through옵션은 다대다 관계를 연결할 중간 테이블을 지정합니다.foreignKey옵션은 중간 테이블에서 사용할 외래 키를 지정합니다.otherKey옵션은 상대 모델의 외래 키를 지정합니다.
이렇게 설정하면 Player와 Team 간에 다대다 관계가 설정되고, 중간 테이블 PlayerTeam을 통해 연결됩니다.
4. 컨트롤러 및 라우터 설정
1) 라우터 정의
sequelize/routes/index.js
const express = require('express'); // Express 모듈 불러오기
const router = express.Router(); // Express 라우터 생성
const controller = require('../controller/Cmain'); // 컨트롤러 모듈 불러오기
// 기본 요청 경로: localhost:PORT
router.get('/', controller.index); // 루트 경로에 대한 GET 요청 처리
module.exports = router; // 라우터 모듈 내보내기
- 라우터를 정의하여 클라이언트의 요청을 처리합니다. 루트 경로(‘/’)에 대한 GET 요청을
controller.index메서드로 전달합니다. 라우터를 모듈로 내보내어 서버에서 사용할 수 있게 합니다.
2) 컨트롤러 설정
sequelize/controller/Cmain.js
exports.index = (req, res) => {
res.render('index'); // index.ejs 템플릿 렌더링
}
index메서드는views/index.ejs템플릿을 렌더링하여 클라이언트에 응답합니다. 이 메서드는 라우터에서 호출되며, 클라이언트의 요청에 대해 동적 HTML 페이지를 제공합니다.
5. EJS 템플릿
sequelize/views/index.ejs
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Sequelize를 배워보자!!</h1>
</body>
</html>