Home [node.js] sequelize(시퀄라이즈)를 사용해서 rdb와 통신하기
Post
Cancel

[node.js] sequelize(시퀄라이즈)를 사용해서 rdb와 통신하기

Api 서버를 구축하고자 할 때 DB와의 연결은 필수적이라고 할 수 있다.
오늘은 시퀄라이즈 라이브러리를 사용해서 DB와 연결하고 CRUD 기능을 수행해보자.

시퀄라이즈 ?

  1. 다양한 RDB와 호환되는 ORM(Object Relational Mapping) 라이브러리. (ex. maria, postgre, sqlite, mssql .. )
  2. 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
20210910001
Sequelize project 초기구성
20210910003
package.json 구성 (version은 달라질 수 있음)

Step 2 실습 프로젝트 개요

(1) 요구사항

구현 목표 : 프로그래밍 언어들과 각 언어를 학습할 수 있는 책들을 저장한다.

(1-1) 구조

  1. 프로그래밍 언어는 언어의 이름과 위키백과 주소를 저장한다.

  2. 프로그래밍 언어 관련 책은 이름과 저자, 가격을 저장한다.

  3. 특정 프로그래밍 언어와 연관된 책이 여러개 존재할 수 있다.

  4. 프로그래밍 언어 관련 책은 특정 한개의 언어에 대해서만 기술한다.

(1-2) 기능

  1. 모든 객체는 하나씩 등록, 수정, 삭제될 수 있다.
  2. 전체 프로그래밍 언어 목록을 이름 기준으로 오름차순 조회하고 각 언어에 연관된 책 중 가장 저렴한 책의 이름을 함께 노출한다.
  3. 프로그래밍 언어 목록 중 하나를 선택하면 , 선택한 언어에 연관된 책 목록을 가격 기준으로 오름차순으로 조회한다.
  4. 전체 책 목록을 이름을 기준으로 오름차순으로 조회한다.
  5. 언어를 삭제하면 해당 언어와 연관된 책들도 함께 삭제된다.

(2) 테이블 설계

(2-1) languages

컬럼명컬럼IDDatatypePKFKNULL비고
PKIdIncrementO X 
이름NameVarchar(64)  Xunique
위키주소Wiki_urlVarchar(128)    

(2-2) books

컬럼명Datatype컬럼IDPKFKNULL비고
PKIncrementIdO X 
이름Varchar(64)Name  Xunique
저자Varchar(64)Writter  X 
가격IntegerPrice  Xdefault 0
언어IDIntegerLanguage_id OX 

(3) ERD

20210910007
erd

(4) Model, Interface 구조

202109100010
Models, interfaces

Step 3 실습 준비 (기초적인 Express 프레임워크 구조 잡기)

Sequelize 사용을 위해 기초적인 서버환경을 구성하자.

(1) routes 구조 잡기 + app.js 파일 생성

20210910006
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 접속 정보 저장하기

20210910008
/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 를 작성하도록 하겠다.

202109100011
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

202109100010
Models, interfaces

(2-2) 기능

  1. 모든 객체는 하나씩 등록, 수정, 삭제될 수 있다.
  2. 전체 프로그래밍 언어 목록을 이름 기준으로 오름차순 조회하고 각 언어에 연관된 책 중 가장 저렴한 책의 이름을 함께 노출한다.
  3. 프로그래밍 언어 목록 중 하나를 선택하면 , 선택한 언어에 연관된 책 목록을 가격 기준으로 오름차순으로 조회한다.
  4. 전체 책 목록을 이름을 기준으로 오름차순으로 조회한다.
  5. 언어를 삭제하면 해당 언어와 연관된 책들도 함께 삭제된다.

(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은 다음과 같다.

methodreturn type
createObject 생성한 행
findAllArray 조건에 부합하는 모든 행
findOneObject 찾은 ROW
updateArray ([ Number 영향받은 행수 ] or postgres 라면 [ Number, Boolean ])
deleteNumber 영향받은 행수

Source

https://github.com/jaeone94/learn_sequelize

references

  • https://sequelize.org/master/class/lib/sequelize.js~Sequelize.html
  • https://sequelize.org/master/index.html

210907 일기

[Web] SSR(서버사이드 렌더링)이 대체 뭐야?