
Node.js에서 사용할 수 있고, 가장 인기있는 ORM
Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more.
Sequelize는 Postgres, MySQL, MariaDB, SQLite, Microsoft SQL Server를 지원하는 Promise 패턴 기반의 Node.js ORM입니다. Solid 트랜잭션, 관계 설정, 즉시 로딩, 지연 로딩, 읽기 전용 복제본 등을 포함해 많은 기능을 제공합니다.
MySQL에 Sequelize 적용
$ npm install sequelize mysql2
$ npm install -g sequelize-cli
프로젝트 루트 경로에서 Sequelize 초기화 진행
$ sequelize init
|-- config
| `-- config.json
|-- migrations
|-- models
| `-- index.js
|-- seeders
연결할 데이터베이스에 대한 정보를 config/config.json 에서 수정
{
"development": {
"username": "root",
"password": null,
"database": "database_development",
"host": "127.0.0.1",
"dialect": "mysql"
// "timezone": "+09:00",
// "define": {
// "freezeTableName": true,
// "timestamps": false
// }
// dialectOptions: {
// decimalNumbers: true,
// },
},
"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"
}
}
이후
createdAt,updatedAt등 DATETIME 형태의 데이터를 삽입하는 경우 UTC 시간으로 입력되는 이슈가 발생하므로 필요한 경우 한국 시간(UTC+09:00) 으로 설정
"timezone": "+09:00"
Sequelize는 기본적으로 테이블명을 복수형으로 만드는데, 복수형이 아닌 Model 이름 그대로 사용하기를 원할 때의 설정
define: { freezeTableName: true }
Sequelize는
SELECT또는INSERT때의 자동으로createdAt과updatedAt을 함께 적용, 이를 Off 할 때의 설정
define: { timestamps: false }
쿼리 결과 값이
String인 이슈
dialectOptions: { decimalNumbers: true }
Sequelize CLI를 통해 모델을 정의하고 마이그레이션을 통해 실제 데이터베이스에 반영이 가능
다음 예제는 name와 age를 가진 User 모델을 생성
$ sequelize model:generate --name User --attributes name:string,age:integer
이를 통해 아래와 같이 총 2개의 파일이 생성됨
models/user.js"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class User 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
}
}
User.init(
{
name: DataTypes.STRING,
age: DataTypes.INTEGER,
},
{
sequelize,
modelName: "User",
}
);
return User;
};
migrations/<timestamp>-create-user.js"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Users", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
name: {
type: Sequelize.STRING,
},
age: {
type: Sequelize.INTEGER,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Users");
},
};
id, createdAt, updatedAt 필드는 정의하지 않아도 자동으로 생성
그리고 up과 down으로 구분
up : 마이그레이션을 진행할 때 수행할 로직down : 적용된 마이그레이션을 되돌릴 때 수행할 로직실제 데이터베이스에 반영하기 위해 마이그레이션 진행
$ sequelize db:migrate
마이그레이션을 진행한 후 실제 데이터베이스를 살펴보면, 앞에서 정의한 필드와 함께 Users 테이블이 생성됨
mysql> DESC Users;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
| age | int | YES | | NULL | |
| createdAt | datetime | NO | | NULL | |
| updatedAt | datetime | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
마이그레이션을 취소할 때에는 명령어 뒤에 undo 삽입
$ sequelize db:migrate:undo
Sequelize는 데이터베이스에서 데이터를 쿼리하는 데 도움이 되는 다양한 방법을 제공
이를 통해 CRUD를 간단하게 구현이 가능
먼저 앞에서 정의한 모델을 불러오는 것이 필요
const { User } = require("./models");
Model.create({key: value})
(async () => {
const user = await User.create({
name: "Jiheon Lee",
age: 23,
});
console.log(user.name); // Jiheon Lee
console.log(user.age); // 23
})();
mysql> SELECT * FROM Users;
+----+------------+------+---------------------+---------------------+
| id | name | age | createdAt | updatedAt |
+----+------------+------+---------------------+---------------------+
| 1 | Jiheon Lee | 23 | 2021-10-06 10:25:45 | 2021-10-06 10:25:45 |
+----+------------+------+---------------------+---------------------+
Model.findAll()Model.findOne({ where: { id: 1 } })(async () => {
const users = await User.findAll();
console.log(users);
})();
[
User {
dataValues: {
id: 1,
name: 'Jiheon Lee',
age: 23,
createdAt: 2021-10-06T01:25:45.000Z,
updatedAt: 2021-10-06T01:25:45.000Z
},
_previousDataValues: {
id: 1,
name: 'Jiheon Lee',
age: 23,
createdAt: 2021-10-06T01:25:45.000Z,
updatedAt: 2021-10-06T01:25:45.000Z
},
_changed: Set(0) {},
_options: {
isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
raw: true,
attributes: [Array]
},
isNewRecord: false
}
]
findAll한 데이터에서 dataValues 이외의 정보 제외
Model.findAll({ raw: true })
dataValues만 리턴되지만, include할 연관 테이블이 없을 때 사용const users = await User.findAll().map(el => el.get({ plain: true }));
위와 동일하게 dataValues만 리턴, include할 연관 테이블이 있을 때 추천
Model.update()
(async () => {
await User.update(
{ name: "John" },
{
where: {
id: 1,
},
}
);
})();
Model.destroy({ where: { id: 1 } })
(async () => {
await User.destroy({
where: {
id: 1,
},
});
})();
ORM을 사용하고자 하는데 기존 DB의 모든 테이블을 모델로 정의하기에는 번거로움
Sequelize에서는 해당 기능을 제공하고 있지 않지만, sequelize-auto에서 제공
$ npm install -g sequelize-auto
$ sequelize-auto -h localhost -d <database> -u <user> -x [password] -p [port] --dialect [dialect] -c [/path/to/config] -o [/path/to/models] -t [tableName]
sequelize-cli를 통해 생성된 config/config.json는 연결할 데이터베이스에 대한 정보들을 포함하는데, 노출되면 이후 큰 문제가 발생할 수 있으므로 .env로 환경변수를 관리가 필요
그리고 sequelize-cli를 사용하려면 config/config.json이 존재하는 경로 내에서만 가능하므로 경로 이동없이 프로젝트 루트 경로에서도 바로 실행할 수 있도록 수정이 필요
먼저 프로젝트 구조화를 위해 sequelize-cli를 통해 생성된 파일들을 src/db/ 안에 이동
|-- src
|-- db
|-- config
| `-- config.json
|-- migrations
|-- models
| `-- index.js
|-- seeders
config.json 파일명을 index.js로 변경한 후 설정들을 export하도록 변경합니다. 이때 환경변수를 .env 파일에서 관리하기 위해 dotenv 설치가 필요
$ npm install dotenv
config/index.jsrequire("dotenv").config();
const env = process.env;
const development = {
username: env.DEV_DB_USERNAME,
password: env.DEV_DB_PASSWORD,
database: env.DEV_DB_DATABASE,
host: env.DEV_DB_HOST,
dialect: env.DEV_DB_DIALECT,
port: env.DEV_DB_PORT,
timezone: env.DB_TIMEZONE,
};
const production = {
username: env.PROD_DB_USERNAME,
password: env.PROD_DB_PASSWORD,
database: env.PROD_DB_DATABASE,
host: env.PROD_DB_HOST,
dialect: env.PROD_DB_DIALECT,
port: env.PROD_DB_PORT,
timezone: env.DB_TIMEZONE,
};
const test = {
username: env.TEST_DB_USERNAME,
password: env.TEST_DB_PASSWORD,
database: env.TEST_DB_DATABASE,
host: env.TEST_DB_HOST,
dialect: env.TEST_DB_DIALECT,
port: env.TEST_DB_PORT,
timezone: env.DB_TIMEZONE,
};
module.exports = { development, production, test };
기존 config.json을 불러오던 models/index.js를 수정
models/index.js"use strict";
const fs = require("fs");
const path = require("path");
const Sequelize = require("sequelize");
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || "development";
// const config = require(__dirname + "/../config/config.json")[env];
const config = require(__dirname + "/../config")[env];
const db = {};
// let sequelize;
// if (config.use_env_variable) {
// sequelize = new Sequelize(process.env[config.use_env_variable], config);
// } else {
// sequelize = new Sequelize(
// config.database,
// config.username,
// config.password,
// config
// );
// }
const sequelize = new Sequelize(
config.database,
config.username,
config.password,
config
);
fs.readdirSync(__dirname)
.filter((file) => {
return (
file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
);
})
.forEach((file) => {
const model = require(path.join(__dirname, file))(
sequelize,
Sequelize.DataTypes
);
db[model.name] = model;
});
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
주석 처리가 되어있는 코드가 이전 코드이며, config.use_env_variable은 npm script에서 실행 환경을 명시해준다면 필요없는 코드이기에 제거
// const env = process.env.NODE_ENV || "development";
const config = require(__dirname + "/../config")[process.env.NODE_ENV];
위의 코드에서 다음과 같이 한 줄로도 표현이 가능
프로젝트 내에 어느곳에서도 sequelize-cli 명령어를 사용할 수 있도록 경로 설정이 필요
따라서 새로운 .sequelizerc 파일을 프로젝트 루트 경로내에 생성
const path = require('path')
module.exports = {
config: path.join(__dirname, 'src/db/config'),
'migrations-path': path.join(__dirname, 'src/db/migrations'),
'seeders-path': path.join(__dirname, 'src/db/seeders'),
'models-path': path.join(__dirname, 'src/db/models'),
}
ERROR: Please install mysql2 package manually
다음과 같은 에러가 발생한다면, sequelize-cli를 global로 설치하여 발생한 문제이다. mysql2 또한 global로 설치가 필요하다.
$ npm install -g mysql2