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.js
require("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