Api 서버를 구축하고자 할 때 DB와의 연결은 필수적이라고 할 수 있다.
오늘은 시퀄라이즈 라이브러리를 사용해서 DB와 연결하고 CRUD 기능을 수행해보자.
시퀄라이즈 ?
- 다양한 RDB와 호환되는 ORM(Object Relational Mapping) 라이브러리. (ex. maria, postgre, sqlite, mssql .. )
- Javascript 문법으로 RDB를 조작할 수 있도록 도와준다.
Step 1 실행환경 세팅
(1) 먼저 DB가 설치되어 있지 않다면 설치해준다. (실습은 mysql로 진행)
windows : https://www.mysql.com/downloads/
mac :
brew install mysql
(2) Project root에서 npm init해서 node 프로젝트를 만든 뒤,
npm 으로 express와 sequelize 그리고 mysql과 Node.js를 연결하기 위한 드라이버인 mysql2를 설치해준다.
1
2
3
user@my-macbook projectRoot % npm init
user@my-macbook projectRoot % npm i express sequelize sequelize-cli mysql2
user@my-macbook projectRoot % npm i -D nodemon
(3) nodemon을 편하게 사용하기 위해 npm의 script 변경
1
2
3
4
5
..
"scripts": {
"start": "nodemon app"
},
..
(4) sequelize 사용하기 위해 프로젝트 구조를 초기화해준다.
1
user@my-macbook projectRoot % npx sequelize init
Sequelize project 초기구성 |
package.json 구성 (version은 달라질 수 있음) |
Step 2 실습 프로젝트 개요
(1) 요구사항
구현 목표 : 프로그래밍 언어들과 각 언어를 학습할 수 있는 책들을 저장한다.
(1-1) 구조
프로그래밍 언어는 언어의 이름과 위키백과 주소를 저장한다.
프로그래밍 언어 관련 책은 이름과 저자, 가격을 저장한다.
특정 프로그래밍 언어와 연관된 책이 여러개 존재할 수 있다.
프로그래밍 언어 관련 책은 특정 한개의 언어에 대해서만 기술한다.
(1-2) 기능
- 모든 객체는 하나씩 등록, 수정, 삭제될 수 있다.
- 전체 프로그래밍 언어 목록을 이름 기준으로 오름차순 조회하고 각 언어에 연관된 책 중 가장 저렴한 책의 이름을 함께 노출한다.
- 프로그래밍 언어 목록 중 하나를 선택하면 , 선택한 언어에 연관된 책 목록을 가격 기준으로 오름차순으로 조회한다.
- 전체 책 목록을 이름을 기준으로 오름차순으로 조회한다.
- 언어를 삭제하면 해당 언어와 연관된 책들도 함께 삭제된다.
(2) 테이블 설계
(2-1) languages
컬럼명 | 컬럼ID | Datatype | PK | FK | NULL | 비고 |
---|---|---|---|---|---|---|
PK | Id | Increment | O | X | ||
이름 | Name | Varchar(64) | X | unique | ||
위키주소 | Wiki_url | Varchar(128) |
(2-2) books
컬럼명 | Datatype | 컬럼ID | PK | FK | NULL | 비고 |
---|---|---|---|---|---|---|
PK | Increment | Id | O | X | ||
이름 | Varchar(64) | Name | X | unique | ||
저자 | Varchar(64) | Writter | X | |||
가격 | Integer | Price | X | default 0 | ||
언어ID | Integer | Language_id | O | X |
(3) ERD
erd |
(4) Model, Interface 구조
Models, interfaces |
Step 3 실습 준비 (기초적인 Express 프레임워크 구조 잡기)
Sequelize 사용을 위해 기초적인 서버환경을 구성하자.
(1) routes 구조 잡기 + app.js 파일 생성
routes 구조 잡기 + app.js 파일 생성 |
(2) languages
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// [/routes/languages/index.js]
// name : languages
// desc : 프로그래밍 언어
// end-point : /languages
const controller = require('./languages.controller');
const router = require('express').Router();
router.get('/', controller.getLanguagesAll); //모든 language를 리턴
router.get('/:id', controller.getLanguagesOne); //특정 language를 리턴
router.post('/', controller.createLanguages); //language를 추가
router.put('/', controller.updateLanguages); //language를 수정
router.delete('/:id', controller.deleteLanguages); //특정 language를 삭제
module.exports = router;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// [/routes/languages/languages.controller.js]
// language service logic
exports.getLanguagesAll = (req, res, next) => {
// db select logic
}
exports.getLanguagesOne = (req, res, next) => {
// db select logic
}
exports.createLanguages = (req, res, next) => {
// db insert logic
}
exports.updateLanguages = (req, res, next) => {
// db update logic
}
exports.deleteLanguages = (req, res, next) => {
// db delete logic
}
(3) books
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// [/routes/books/index.js]
// name : books
// desc : 프로그래밍 언어 관련 책
// end-point : /books
const controller = require('./books.controller');
const router = require('express').Router();
router.get('/', controller.getBookAll); //모든 book을 리턴
router.post('/', controller.createBook); //book을 추가
router.put('/', controller.updateBook); //book을 수정
router.delete('/:id', controller.deleteBook); //book을 삭제
module.exports = router;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// [/routes/books/books.controller.js]
// book service logic
exports.getBookAll = (req, res, next) => {
// db select logic
}
exports.createBook = (req, res, next) => {
// db insert logic
}
exports.updateBook = (req, res, next) => {
// db update logic
}
exports.deleteBook = (req, res, next) => {
// db delete logic
}
(4) app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const express = require('express');
const app = express();
const routes = require('./routes');
app.set('port', 3000);
app.use(express.json());
app.use('/', routes);
//404 미들웨어
app.use((req, res, next) => {
res.status(404).send(`${req.method} ${req.url} API not found !`);
});
//에러처리 미들웨어
app.use((err, req, res, next) => {
res.status(500).json({
message : err.message
})
});
app.listen(app.get('port'), () => {
console.log('Server is running on ', app.get('port'));
})
Step 4 Sequelize로 DB와 세션 맺기
이제 DB와 세션을 맺어 서비스 로직을 구현할 준비를 해보자.
(1) config.json 에서 DB 접속 정보 저장하기
/config/config.json 은 npx sequelize init를 할 때 자동으로 생성해줬다. |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// [/config/config.json]
{
"development": { // node 실행환경에 따라 접속환경을 다르게 설정할 수 있다.
"username": "root", //db user name
"password": null, //db user password
"database": "mydatabase", //database 이름 (없다면 만들어주자. CREATE SCHEMA `mydatabase` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ;)
"host": "127.0.0.1", //localhost
"dialect": "mysql" //dbms
},
"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"
}
}
(2) /models/index.js 코드 살펴보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
'use strict';
const fs = require('fs'); //파일시스템 읽고 쓰기위한 라이브러리
const path = require('path'); //로컬 경로 탐색을 위한 라이브러리
const Sequelize = require('sequelize'); // Sequelize 클래스 라이브러리
const basename = path.basename(__filename); // 현재 이 파일이름 'index.js' (아래에서 현재 파일을 제외하기 위해 사용)
const env = process.env.NODE_ENV || 'development'; // 현재 node 실행 환경을 가져옴
const config = require(__dirname + '/../config/config.json')[env]; // 위에서 정의한 db접속 환경설정(config.json)이 담긴 object를 가져옴
const db = {}; // 빈 오브젝트 선언
let sequelize; // 빈 변수 선언 (세션이 담길 변수)
if (config.use_env_variable) { // db 환경설정 파일(config.json)에서 (보안등의 이유로) 접속 정보를 환경변수에 담아 이 것을 읽어올 것인지 정의할 수 있음.
sequelize = new Sequelize(process.env[config.use_env_variable], config); //db와 새로운 세션을 맺는다. (환경변수 읽음)
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config); //db와 새로운 세션을 맺는다. (config.json 읽음)
}
// 모델 생성 ! (모델은 데이터베이스의 테이블 정보임)
fs
.readdirSync(__dirname) // 현재 디렉터리의 파일들을 모두 읽어옴.
.filter(file => {
// .으로 시작하지 않고 (숨김파일) && index.js(자기자신)파일이 아니고 && .js 확장자를 가진 파일만 남긴다.
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => { // 남은 파일들은 모델(Model)이라고 간주하고 하나씩 불러와서 db object에 담는다.
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); // << ES6 문법임 !!!
db[model.name] = model; // 오브젝트에 모델들을 담는다.
});
// 각 모델들을 돌면서 모델간의 관계를 정의하는 함수를 동작시킴. (associate)
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db); // 관계를 정의하기 위해선 다른 모델을 참고해야하기 때문에 모델들이 담긴 db를 파라미터로 넘긴다.
}
});
db.sequelize = sequelize; // 세션과
db.Sequelize = Sequelize; // Class를 db에 추가
module.exports = db; // export
(2-1) [코드 수정]
Node.js는 ES6 문법을 완벽히 지원하지 않기 때문에 (common.js 방식을 따른다.)
1
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); // << ES6 문법임 !!!
해당 코드를
1
const model = require(path.join(__dirname, file)).init(sequelize, Sequelize.DataTypes); // 이렇게 변경해주자.
이렇게 변경해주자.
ps. ES6 지원하도록 하려면 babel 을 설치 후 환경설정 필요
(3) 모델 작성 문법 배우기
모델은 두가지 방법으로 정의할 수 있다.
(3-1) sequelize.define 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({접속 정보});
// sequelize.define('모델이름', attributes, options) (모델을 반환한다.)
const User = sequelize.define('User', {
// 테이블의 컬럼정보(attribute)를 정의
column1: {
type: DataTypes.INTEGER.UNSIGNED, // 컬럼의 데이터타입
allowNull: false // Null 허용여부
},
column2: {
type: DataTypes.STRING(20)
// allowNull 의 기본값은 true다.
}
}, {
// 테이블 속성 및 모델 옵션 정의
});
또는
(3-2) Model 상속 Class 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const { Sequelize, DataTypes, Model } = require('sequelize');
const sequelize = new Sequelize({접속 정보});
// 1. Model을 상속받은 class를 만들고
// 2. init(attributes, options)를 호출한다.
// **3. 모델 이름과, 세션인스턴스(sequelize)는 options에 들어감.
class User extends Model {} //Sequelize Model 클래스를 상속받은 클래스 작성
//클래스의 init함수 호출 (모델을 반환한다.)
const User = User.init({
// 테이블의 컬럼정보(attribute)를 정의
column1: {
type: DataTypes.STRING, // 컬럼의 데이터타입
allowNull: false // Null 허용여부
},
column2: {
type: DataTypes.STRING
// allowNull 의 기본값은 true다.
}
}, {
// 테이블 속성 및 모델 옵션 정의
sequelize, // define과 다르게 sequelize의 함수를 사용하는게 아니기 때문에, 세션 instance를 넘겨줘야한다.
modelName: 'User' // 모델 이름을 설정해줘야한다. define은 첫번째 파라미터로 설정했음.
});
(4) Language & Book 모델 작성하기
본 포스트에서는 두번째 방법인 Model 상속한 Class 를 작성하도록 하겠다.
models 구성 |
먼저 /models 디렉터리에 language.js 와 book.js 파일을 생성한다.
(4-1) language.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const Sequelize = require('sequelize');
module.exports = class Language extends Sequelize.Model { //모델을 상속받은 클래스를 만든다.
static init(sequelize, DataTypes) { // Model 클래스 안에 정의되어 있는 함수를 override한다.
return super.init({
// id 컬럼은 자동으로 Sequelize가 만들어준다.
name : {
type : DataTypes.STRING(64), //VARCHAR(64)
allowNull : false, // NOT NULL
},
wiki_url : {
type : DataTypes.STRING(128), //VERCHAR(128)
allowNull : true,
}
}, {
sequelize,
timestamps: false, // true : 생성된 시간과 수정된 시간을 자동으로 등록해주는 createdAt, updatedAt 컬럼을 자동 추가해준다.
underscored : true, // true : 자동으로 추가되는 컬럼들의 이름을 스네이크케이스(created_at)로 만든다. / false : 카멜케이스(createdAt)
modelName : 'Language', // 모델 이름 정의 (보통 단수형 파스칼케이스로 정의한다.)
tableName : 'languages', // 테이블 이름 (따로 정의하지 않으면 모델명의 복수형 단어로 만듬)
paranoid : false, // true : deletedAt 컬럼을 만들고 삭제될 때, 실제로 삭제하지 않고 deletedAt에 값을 준다.
charset : 'utf8',
collate : 'utf8_general_ci'
});
}
}
(4-2) book.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const Sequelize = require('sequelize');
module.exports = class Book extends Sequelize.Model {
static init(sequelize, DataTypes) {
return super.init({
name : {
type : DataTypes.STRING(64),
allowNull : false,
},
writter : {
type : DataTypes.STRING(64),
allowNull : false,
},
price : {
type : Sequelize.INTEGER,
allowNull : false,
defaultValue : 0, //미지정시 기본값 0
}
// 외래키는 여기서 정의하지 않고. associate 함수를 통해 만든다.
}, {
sequelize,
timestamps: false,
underscored : true,
modelName : 'Book',
tableName : 'books',
paranoid : false,
charset : 'utf8',
collate : 'utf8_general_ci'
});
}
}
(4-3) init 함수를 override한 이유 ?
Model Class가 가지고 있는 init 함수는 원래 init(attributes, options) 이렇게 생겼고, 어트리뷰트와 속성을 파라미터로 가진다.
현재 구조에선 models/index.js 에서 세션 인스턴스(sequelize)가 정의되고, index.js에서 각 모델을 읽어서 순차적으로 init함수를 호출한다.
init함수 동작을 위해선 DB 접속 세션인 sequelize 인스턴스가 필요한데. 이 것은 index.js에 정의되어 있으니 이를 각 모델에 넘겨줄 필요가 있었고. 어트리뷰트와 옵션들을 index.js에서 모두 넘겨주자니 코드가 매우 더러워질 것이므로 이 것들은 넘겨주지 않을 필요가 있었다.
따라서 init함수의 파라미터를 변경해 sequelize를 넘겨주고 내부에서 super.init 함수로 부모의 init 역할을 수행하도록 한 것 (그리고 만들어진 모델을 리턴).
(4-4) 테이블간 관계 정의하기 (Foreign Key)
관계형 데이터베이스의 핵심인 테이블간의 관계를 정의해보자.
관계를 정의하기 위한 함수는 각 Model에 위치해있다.
1
2
3
4
5
6
7
8
9
10
11
// 1:1 관계 (1:1 관계이더라도 부모자식 관계가 있음. 외래키가 정의된 테이블이 자식테이블임)
ParentModel.hasOne(ChildModel, { foreignKey : 'fk_name', sourceKey : 'parent_tables_pk' }); //부모 모델
ChildModel.belongsTo(ParentModel, { foreignKey : 'fk_name', targetKey : 'parent_tables_pk' }); //자식 모델
// 1:N 관계
ParentModel.hasMany(ChildModel, { foreignKey : 'fk_name', sourceKey : 'parent_tables_pk' }); //부모 모델
ChildModel.belongsTo(ParentModel, { foreignKey : 'fk_name', targetKey : 'parent_tables_pk' }); //자식 모델
// N:N 관계 (1:1, 1:N과 다르게 관계 '테이블'을 생성함)
Model1.belongsToMany(Model2, { through : 'relation_table_name' });
Model2.belongsToMany(Model1, { through : 'relation_table_name' });
현재 실습 프로젝트는 언어와 책의 관계가 1:N 관계다. 즉, 하나의 언어에 여러 책이 존재할 수 있는 것이다.
관계정의 함수는 각 모델 클래스 안에 함수로 정의한다.
language.js
1
2
3
4
5
6
7
8
module.exports = class Language extends Sequelize.Model {
static init(sequelize, DataTypes) {
//...
}
static associate(db) {
db.Language.hasMany(db.Book, { foreignKey : 'language_id', sourceKey : 'id'}); //Language가 부모
}
}
book.js
1
2
3
4
5
6
7
8
9
10
module.exports = class Book extends Sequelize.Model {
static init(sequelize, DataTypes) {
//...
}
static associate(db) {
db.Book.hasMany(db.Language, { foreignKey : 'language_id', targetKey : 'id', onDelete : 'cascade'});
//Book은 자식 (onDelete : 'cascade' 옵션을 주어서 부모 객체가 삭제되었을 때 같이 삭제되도록 함.)
//기본값 : ON DELETE SET NULL / ON UPDATE SET CASCADE
}
}
(5) 모델 동기화 하기
(5-1) /app.js
1
2
3
4
5
6
7
8
9
...
const { sequelize } = require('./models'); // models/index.js 가 export한 db.sequelize를 가져옴.
sequelize.sync({옵션}) // 동기화 및 연결
.then(()=> { // db와 connection을 맺는것은 비동기 작업이다.(불확실성) 따라서 Primise 를 리턴한다.
console.log('데이터베이스가 연결됨 !');
}).catch((err)=>{
console.log(err);
});
...
문법은 간단하다. models의 index.js의 sequelize 세션을 가져와서 .sync() 함수만 동작시키면 된다.
Primise를 리턴하기 때문에 .then().catch() 로 성공/실패시 수행할 동작을 정의하자.
1
2
3
4
5
6
Server is running on 3000
Executing (default): CREATE TABLE IF NOT EXISTS `languages` (`id` INTEGER NOT NULL auto_increment , `name` VARCHAR(64) NOT NULL, `wiki_url` VARCHAR(64), PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci;
Executing (default): SHOW INDEX FROM `languages` FROM `mydatabase`
Executing (default): CREATE TABLE IF NOT EXISTS `books` (`id` INTEGER NOT NULL auto_increment , `name` VARCHAR(64) NOT NULL, `writter` VARCHAR(64) NOT NULL, `price` INTEGER NOT NULL DEFAULT 0, `language_id` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`language_id`) REFERENCES `languages` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci;
Executing (default): SHOW INDEX FROM `books` FROM `mydatabase`
database connected
정상적으로 DB와 세션을 맺었다면 콘솔에 동기화 옵션에 따른 쿼리가 출력된다. (***모델이 하나도 없으면 SELECT 1+1 AS result 가 출력된다.)
(5-2) sync() 동기화 옵션
sequelize.sync() 는 파라미터로 옵션 오브젝트를 추가할 수 있는데. 옵션 종류는 다음과 같다.
옵션 | 동작 |
---|---|
옵션없음 | 같은 이름 테이블 없으면 CREATE |
{ force : true } | 같은 이름 테이블 있으면 DROP 하고 CREATE |
{ alter : true } | 같은 이름 테이블 없으면 CREATE / 있으면 ALTER |
옵션없음 : 모델의 테이블명을 스키마에서 스캔하고, 스캔한 이름의 테이블이 스키마에 존재하지 않을시 테이블을 생성한다. (CREATE TABLE IF NOT EXISTS [테이블명] )
{ force : true } : 모델의 테이블명을 스키마에서 스캔하고, 스캔한 이름의 테이블이 스키마에 존재할 때, 해당 테이블을 삭제(DROP)하고 모델에서 정의한 테이블을 생성한다. ( DROP TABLE IF EXISTS [테이블명]; CREATE TABLE IF NOT EXISTS [테이블명] )
{ alter : true } : 모델의 테이블명을 스키마에서 스캔하고, 스캔한 이름의 테이블이 스키마에 존재할 때, 검색한 테이블과 모델에서 정의한 테이블의 차이를 찾아서 테이블을 수정해준다. (ALTER TABLE ADD COLUMN or ALTER TABLE CHANGE)
Step 5 Sequelize 로 DML 사용하기 (select, insert, update, delete)
Sequelize는 자바스크립트 언어로 데이터베이스 조작을 가능하게한다.
당연하게도 데이터 조작 언어인 DML 의 사용도 자바스크립트로 작성할 수 있는데
간단한 문법부터 알아보자.
(1) 사용법
(1-1) create (생성)
1
2
3
4
5
6
7
// Model.create({등록할 row의 attributes값}, { fields : [등록을 허용할 attributes]});
Model.create({
attribute1 : 'value',
attribute2 : 20,
attribute3 : true,
},{ fields: ['attribute1, attribute2'] });
// INSERT INTO models (attribute1, attribute2) VALUES ('value', 20);
(1-2) read (조회)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
const { Op } = require('sequelize'); //Op = Operation
Model.findAll({
attributes : ['attribute1', 'attribute2'], // 조회 컬럼. 미지정시 *
where : { // 조회 조건
attribute2 : {
[Op.gte] : 20 // attribute2 >= 20
},
attribute3 : true, // where에 추가한 조건 만큼 AND 연산
},
order : [['attribute2', 'DESC'], ['attribute1', 'ASC']] //정렬순서. 2차원 배열로 정의
limit : 10, //조회 행 수
ofset : 1, //몇번째 행 부터 조회할 것인지
// 관계쿼리 (fk 연결관계에 있는 테이블의 행을 함께 조회가능.)
// 여러 관계가 있을 수 있음으로 order와 마찬가지로 Array type임.
include : [{
model : 'childOrParentModel', //관계된 모델명
attributes : ['id', 'name'], //일반 select와 마찬가지로 attributes, where 추가 가능
where : {
id : 1
}
}]
});
// SELECT attribute1, attribute2 FROM models WHERE attribute2 >= 20 AND attribute3 = true
// ORDER BY attribute2 DESC, attribute1 ASC LIMIT 10 OFSET 1;
Model.findOne({}); // findOne 하면 조건에 부합하는 첫번째 행을 Object로 리턴. (findAll은 당연하게도 Array 리턴)
// ***다양한 논리연산자***
// ES6의 계산된 속성명 (Computered property names)을 이용해 각 논리 연산자를 사용할 수 있다.
Model.findAll({
where: {
[Op.and]: [{ a: 5 }, { b: 6 }], // (a = 5) AND (b = 6) // where { a : 5, b : 6 } 으로 생략가능
[Op.or]: [{ a: 5 }, { b: 6 }], // (a = 5) OR (b = 6)
c : [1, 2, 3], // c IN (1, 2, 3);
someAttribute: {
// 기본 논리연산자
[Op.eq]: 3, // = 3
[Op.ne]: 20, // != 20
[Op.is]: null, // IS NULL
[Op.not]: true, // IS NOT TRUE
[Op.or]: [5, 6], // (someAttribute = 5) OR (someAttribute = 6)
// 숫자 비교 연산자
[Op.gt]: 6, // > 6
[Op.gte]: 6, // >= 6
[Op.lt]: 10, // < 10
[Op.lte]: 10, // <= 10
[Op.between]: [6, 10], // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15
// 그 외 자주사용하는 연산자
[Op.all]: sequelize.literal('SELECT 1'), // > ALL (SELECT 1)
[Op.in]: [1, 2], // IN [1, 2]
[Op.notIn]: [1, 2], // NOT IN [1, 2]
[Op.like]: '%hat', // LIKE '%hat'
}
}
});
(1-3) update (수정)
1
2
3
4
5
6
7
8
9
10
11
// Model.update({수정할 attributes}, {조건});
Model.update({
attribute1 : 'newValue',
attribute2 : 30,
attribute3 : false,
}, {
where : {
attribute3 : true,
}
});
// UPDATE models SET attribute1 = 'newValue', attribute2 = 30, attribute3 = false WHERE attribute3 = true;
(1-4) destroy (삭제)
1
2
3
4
5
6
7
8
9
10
11
// Model.destroy({조건});
Model.destroy({
where : { //조건은 반드시 where 속성의 object안에 정의되어 있음을 주의.
attribute2 : {[Op.lt] : 20}
}
});
// DELETE FROM models WHERE attribute2 < 20;
Model.destroy({
truncate: true // truncate 해서 전부 삭제 가능(delete는 공간반납 X, truncate는 공간까지 반납. 조건못줌.)
})
(2) API 서비스로직 구현
이제 설계한대로 각 API의 서비스 로직을 Sequelize의 DML 함수들로 구현해보자
(2-1) Interface
Models, interfaces |
(2-2) 기능
- 모든 객체는 하나씩 등록, 수정, 삭제될 수 있다.
- 전체 프로그래밍 언어 목록을 이름 기준으로 오름차순 조회하고 각 언어에 연관된 책 중 가장 저렴한 책의 이름을 함께 노출한다.
- 프로그래밍 언어 목록 중 하나를 선택하면 , 선택한 언어에 연관된 책 목록을 가격 기준으로 오름차순으로 조회한다.
- 전체 책 목록을 이름을 기준으로 오름차순으로 조회한다.
- 언어를 삭제하면 해당 언어와 연관된 책들도 함께 삭제된다.
(2-3) languages.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
const path = require('path');
const { Language, Book } = require(path.join(process.env.PWD, '/models'));
exports.getLanguagesAll = (req, res, next) => {
Language.findAll({
order : [['name', 'ASC']],
include : [{
model : Book,
attributes : ['name'],
order : [['price', 'ASC']],
limit : 1,
}],
}).then((response) => {
res.json({
message : 'success',
data_list : response,
});
}).catch((error) => {
console.log(error);
const err = new Error(error);
next(err);
});
}
exports.getLanguagesOne = (req, res, next) => {
Language.findOne({
where : {
id : req.params.id,
},
include : [{
model : Book,
}],
}).then((response) => {
response.Books.sort((a, b) => a.price - b.price);
res.json({
message : 'success',
data : response,
});
}).catch((error) => {
console.log(error);
const err = new Error(error);
next(err);
});
}
exports.createLanguages = (req, res, next) => {
Language.create(req.body, {
fields : ['name', 'wiki_url']
}).then((response) => {
res.json({
message : 'success',
data : response
});
}).catch((error) => {
console.log(error);
const err = new Error(error);
next(err);
});
}
exports.updateLanguages = (req, res, next) => {
Language.update({
name : req.body.name,
wiki_url : req.body.wiki_url,
}, {
where : {
id : req.body.id,
}
}).then((response) => {
res.json({
message : 'success',
count : response[0] //영향을 받은 행 수
});
}).catch((error) => {
console.log(error);
const err = new Error(error);
next(err);
});
}
exports.deleteLanguages = (req, res, next) => {
Language.destroy({
where : {
id : req.params.id,
}
}).then((response) => {
res.json({
message : 'success',
count : response //영향을 받은 행 수
});
}).catch((error) => {
console.log(error);
const err = new Error(error);
next(err);
});
}
(2-4) books.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
const path = require('path');
const { Book } = require(path.join(process.env.PWD, '/models'));
exports.getBookAll = (req, res, next) => {
Book.findAll({
order : [['name', 'ASC']]
}).then((response) => {
res.json({
message : 'success',
data_list : response
});
}).catch((error) => {
console.log(error);
const err = new Error(error);
next(err);
})
}
exports.createBook = (req, res, next) => {
Book.create(req.body
, {
fields : ['name', 'writter', 'price', 'language_id']
}).then((response) => {
res.json({
message : 'success',
data : response
});
}).catch((error) => {
console.log(error);
const err = new Error(error);
next(err);
})
}
exports.updateBook = (req, res, next) => {
Book.update({
name : req.body.name,
writter : req.body.writter,
price : req.body.price,
language_id : req.body.language_id,
}, {
where : {
id : req.body.id
}
}).then((response) => {
res.json({
message : 'success',
count : response[0]
});
}).catch((error) => {
console.log(error);
const err = new Error(error);
next(err);
})
}
exports.deleteBook = (req, res, next) => {
Book.destroy({
where : {
id : req.params.id,
}
}).then((response) => {
res.json({
message : 'success',
count : response //영향을 받은 행 수
});
}).catch((error) => {
console.log(error);
const err = new Error(error);
next(err);
});
}
DML 함수 역시 비동기 함수기 때문에 Promise를 리턴하기 때문에
Then / Catch 로 결과 처리를 해주거나, async /await를 써야한다.
각각의 return type은 다음과 같다.
method | return type |
---|---|
create | Object 생성한 행 |
findAll | Array 조건에 부합하는 모든 행 |
findOne | Object 찾은 ROW |
update | Array ([ Number 영향받은 행수 ] or postgres 라면 [ Number, Boolean ]) |
delete | Number 영향받은 행수 |
Source
https://github.com/jaeone94/learn_sequelize
references
- https://sequelize.org/master/class/lib/sequelize.js~Sequelize.html
- https://sequelize.org/master/index.html