노알못 Node.js 정리: 데이터베이스(MariaDB)

Table of Content

이 글은 Node.js 교과서(조현영 저) 라는 책으로 공부한 내용을 정리한 글입니다. 이 책에선 MySQL을 설명하지만 개인적으로 MariaDB 사용법에 대해 알아보고자 합니다.

MariaDB 설치 및 설정(MacOS)

Homebrew 설치

패키지 관리자인 Homebrew 설치 필요

  • 설치 명령어: /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  • 설치 후 Homebrew에서 관리하는 패키지의 최신버전이 있는지 확인: brew update

MariaDB 설치

설치할 수 있는 MariaDB 패키지 목록 및 버전 확인: brew search mariadb

➜  ~ brew search mariadb  # Homebrew에서 mariadb 패키지 검색
==> Formulae
mariadb                    mariadb-connector-odbc     mariadb@10.2
mariadb-connector-c        mariadb@10.1               mariadb@10.3

==> Casks
navicat-for-mariadb

MariaDB 10.3 버전 설치: brew install mariadb@10.3

➜  ~ brew install mariadb@10.3  # MariaDB 10.3 버전 설치

# (대충 설치 중 메시지...)

To enable mecab-ipadic dictionary, add to /usr/local/etc/mecabrc:
  dicdir = /usr/local/lib/mecab/dic/ipadic
==> openssl@1.1
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because openssl/libressl is provided by macOS so don't link an incompatible version.

If you need to have openssl@1.1 first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

==> mariadb@10.3
A "/etc/my.cnf" from another install may interfere with a Homebrew-built
server starting up correctly.

MySQL is configured to only allow connections from localhost by default

To connect:
    mysql -uroot

mariadb@10.3 is keg-only, which means it was not symlinked into /usr/local,
because this is an alternate version of another formula.

If you need to have mariadb@10.3 first in your PATH run:
  echo 'export PATH="/usr/local/opt/mariadb@10.3/bin:$PATH"' >> ~/.zshrc

For compilers to find mariadb@10.3 you may need to set:
  export LDFLAGS="-L/usr/local/opt/mariadb@10.3/lib"
  export CPPFLAGS="-I/usr/local/opt/mariadb@10.3/include"

To have launchd start mariadb@10.3 now and restart at login:
  brew services start mariadb@10.3
Or, if you don't want/need a background service you can just run:
  /usr/local/opt/mariadb@10.3/bin/mysql.server start

MariaDB 경로(환경변수) 설정

mysql -uroot 명령어로 MariaDB 실행 시 command not found라고 나오면서 실행이 안 되면 MariaDB 경로에 대한 환경변수를 등록해야 함

  • MariaDB를 설치한 후 다음과 같은 메시지가 출력됨. 이는 MariaDB 경로를 환경변수에 추가하기 위한 명령어를 실행하라는 메시지.
If you need to have mariadb@10.3 first in your PATH run:
  echo 'export PATH="/usr/local/opt/mariadb@10.3/bin:$PATH"' >> ~/.zshrc

위에서 알려준 echo 'export PATH="/usr/local/opt/mariadb@10.3/bin:$PATH"' >> ~/.zshrc 명령어 실행

  • 참고로 ZShell을 사용하는 경우 명령어의 끝 부분이 ~/.zshrc로 되어 있음.
  • 기본적으로 흔히 사용되는 Bash Shell인 경우 ~/.bash_profile 이라고 나올 듯…

위의 명령어를 적용한 후 Zshell을 사용하는 경우 source ~/.zshrc 명령어 실행

  • Bash Shell을 사용한다면 source ~/.bash_profile 명령어를 실행하면 될 듯…

MariaDB 실행

  • MariaDB 실행: mysql.server start
➜  ~ mysql.server start  # MariaDB 실행
Starting MariaDB
.200112 14:21:16 mysqld_safe Logging to '/usr/local/var/mysql/Ing-Yeo-MacBook-Pro.local.err'.
200112 14:21:16 mysqld_safe Starting mysqld daemon with databases from /usr/local/var/mysql
 SUCCESS!
  • MariaDB 상태 확인: mysql.server status
  • MariaDB 재시작: mysql.server restart
  • MariaDB 중지: mysql.server stop

MariaDB 루트 패스워드 설정

  • 방법: DB Table을 변경, mysqladmin 유틸리티 사용 등
  • 여기서는 SET PASSWORD 명령어를 사용
    1. 비밀번호 설정이 안 된 MariaDB 루트 계정 로그인: mysql -uroot
    2. SET PASSWORD FOR 'root'@'localhost'=password('P@sSw0rd!');명령어 실행
    3. FLUSH PRIVILEGES; 명령어 실행
➜  ~ mysql -uroot  # 패스워드 설정이 안 된 MariaDB 루트 계정 로그인
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 12
Server version: 10.3.20-MariaDB Homebrew

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> set password for 'root'@'localhost'=password('P@sSw0rd!');  # 루트 패스워드 설정
Query OK, 0 rows affected (0.000 sec)

MariaDB [(none)]> FLUSH PRIVILEGES;  # 변경한 설정을 적용하기 위한 명령어
Query OK, 0 rows affected (0.000 sec)

MariaDB [(none)]> exit  # MariaDB 종료
Bye
➜  ~

비밀번호 설정된 MariaDB 루트 계정 로그인: mysql -uroot -p

MySQL Workbench로 MariaDB 접속하기

MySQL Workbench를 설치 및 실행한 후 새로운 MySQL Connections를 다음과 같이 추가

  • Hostname: 127.0.0.1
  • Port: 3306 (MariaDB의 기본 포트)
  • Username: root
  • Password: 적절히 입력

Test Connection 버튼을 눌러 Successfully made the MySQL connection 메시지가 나온다면 정상

  • Incompatible/nonstandard server version or connection protocol detected (10.3.20).라는 경고 메시지가 뜨긴 하지만 정상적으로 작동하긴 함.
  • MariaDB와 MySql Workbench 간의 호환성 경고인 듯…

데이터베이스 및 테이블 생성

데이터베이스 생성

  • MariaDB에서 데이터베이스(스키마) 생성: CREATE SCHEMA db_name;
  • 생성된 데이터베이스 사용: USE db_name;
MariaDB [(none)]> CREATE SCHEMA nodejs;  # nodejs라는 이름의 데이터베이스(스키마) 생성
Query OK, 1 row affected (0.004 sec)

MariaDB [(none)]> USE nodejs;  # nodejs 데이터베이스(스키마) 사용
Database changed

테이블 생성

# nodejs라는 데이터베이스 안에 users라는 테이블 생성
MariaDB [nodejs]> CREATE TABLE nodejs.users(
    -> id INT NOT NULL AUTO_INCREMENT,
    -> name VARCHAR(20) NOT NULL,
    -> age INT UNSIGNED NOT NULL,
    -> married TINYINT NOT NULL,
    -> comment TEXT NULL,
    -> created_at DATETIME NOT NULL DEFAULT now(),
    -> PRIMARY KEY(id),
    -> UNIQUE INDEX name_UNIQUE(name ASC))
    -> COMMENT='사용자 정보'
    -> DEFAULT CHARSET=utf8
    -> ENGINE=InnoDB;
Query OK, 0 rows affected (0.032 sec)
# nodejs라는 데이터베이스 안에 comments라는 테이블 생성
MariaDB [nodejs]> CREATE TABLE nodejs.comments(
    -> id INT NOT NULL AUTO_INCREMENT,
    -> commenter INT NOT NULL,
    -> comment VARCHAR(100) NOT NULL,
    -> created_at DATETIME NOT NULL DEFAULT now(),
    -> PRIMARY KEY(id),
    -> INDEX commenter_idx(commenter ASC),
    -> CONSTRAINT commenter
    -> FOREIGN KEY(commenter)
    -> REFERENCES nodejs.users(id)
    -> ON DELETE CASCADE
    -> ON UPDATE CASCADE)
    -> COMMENT='댓글'
    -> DEFAULT CHARSET=utf8
    -> ENGINE=InnoDB;
Query OK, 0 rows affected (0.017 sec)

위의 쿼리문에서 사용한 자료형

  • INT: 정수
    • 소수: FLOAT 또는 DOUBLE
  • VARCHAR(자릿수): 가변길이 문자열. 해당 자릿수까지 문자열을 넣을 수 있음.
    • CHAR(자릿수)는 반드시 자릿수만큼 문자열을 넣어야 하며 그렇지 않은 경우 부족한 자릿수만큼 스페이스가 채워짐.
  • TEXT: 긴 글을 저장할 때 사용
    • 몇백 자 이내의 문자열은 VARCHAR를, 그 보다 길면 TEXT로 주로 처리
  • TINYINT: -127 ~ 128 까지의 정수
    • 1 또는 0만 저장한다면 참거짓 판별을 위한 불 값(Boolean)과 같은 역할을 할 수 있음
  • DATETIME: 날짜 및 시간에 대한 정보
    • DATE: 날짜 정보만 포함
    • TIME: 시간 정보만 포함

위의 쿼리문에서 자료형 뒤에 사용된 컬럼(열) 옵션

  • NULL: 빈 값 허용
    • NOT NULL: 빈 값을 허용하지 않음
  • AUTO_INCREMENT: 숫자를 저절로 상승
  • UNSIGNED: 숫자 자료형에만 적용되는 옵션. 음수값 무시.
    • 이 옵션을 적용하면 숫자 자료형의 범위가 양수로 확장됨 (예: INT의 범위는 대략 -21억 ~ 21억인 반면 UNSIGNED INT의 범위는 대략 0 ~ 42억)
    • 나이처럼 음수가 나올 수 없는 컬럼에 적용
  • ZEROFILL: 숫자의 자릿수가 고정되어 있을 때 비어있는 자리를 0으로 채움
    • 예: INT(4)인데 숫자 1을 넣었다면 0001이 됨
  • DEFAULT value: 해당 컬럼에 값을 지정하지 않으면 기본값을 대신 넣어줌
    • 예: DEFAULT now()는 현재 시각을 기본값으로 넣을 것을 명시
  • PRIMARY KEY(컬럼명): 테이블에 저장된 값(행)을 식별하기 위한 기본키 설정
  • UNIQUE INDEX: 해당 값이 고유해야 하는지 결정
    • 예: UNIQUE INDEX name_UNIQUE(name ASC)는 인덱스의 이름을 name_UNIQUE로 하여 name 컬럼을 오름차순(ASC)으로 기억할 것임을 명시
    • PRIMARY KEY나 UNIQUE INDEX는 데이터베이스가 별도로 컬럼(열)을 관리하므로 조회 시 속도가 빨라짐
    • PRIMARY KEY는 자동으로 UNIQUE INDEX를 포함하므로 따로 명시하지 않음
  • CONSTRAINT 제약조건명 FOREIGN KEY(컬럼명) REFERENCES 참고할_컬럼명: 외래키(다른 테이블의 기본키를 저장하는 컬럼) 지정
    • 위에서 실행한 쿼리문은 commenter(댓글 작성자의 ID) 컬럼에 users 테이블의 id 컬럼 값을 저장
  • ON UPDATE CASCADE: 정보 수정 시 그것과 연관된 정보도 같이 수정
  • ON DELETE CASCADE: 정보 삭제 시 그것과 연관된 정보도 같이 삭제

위의 쿼리문에서 사용한 테이블 자체 설정

  • COMMENT: 테이블 보충 설명
  • DEFAULT CHARSET: 문자열 기본 인코딩 설정
    • utf8이 아니면 한글이 입력되지 않음
  • ENGINE: DB 엔진 설정
    • 종류: MyISAM, InnoDB 등

만들어진 테이블의 구조 확인

DESC table_name;

MariaDB [nodejs]> DESC users;  # users 테이블 구조 확인
+------------+------------------+------+-----+---------------------+----------------+
| Field      | Type             | Null | Key | Default             | Extra          |
+------------+------------------+------+-----+---------------------+----------------+
| id         | int(11)          | NO   | PRI | NULL                | auto_increment |
| name       | varchar(20)      | NO   | UNI | NULL                |                |
| age        | int(10) unsigned | NO   |     | NULL                |                |
| married    | tinyint(4)       | NO   |     | NULL                |                |
| comment    | text             | YES  |     | NULL                |                |
| created_at | datetime         | NO   |     | current_timestamp() |                |
+------------+------------------+------+-----+---------------------+----------------+
6 rows in set (0.006 sec)

CRUD 작업

Create(생성)

테이블에 데이터(행)를 넣는 쿼리문: INSERT INTO table_name (column_1, column_2, ..., column_n) VALUES (value_1, value_2, ..., value_n)

MariaDB [nodejs]> INSERT INTO nodejs.users(name, age, married, comment) VALUES ('pengsu', 10, 0, '안녕요?ㅎ');
Query OK, 1 row affected (0.001 sec)

Read(조회)

SELECT 문으로 테이블 내의 데이터(행) 조회

MariaDB [nodejs]> select * from users;
+----+--------+-----+---------+---------------+---------------------+
| id | name   | age | married | comment       | created_at          |
+----+--------+-----+---------+---------------+---------------------+
|  1 | pengsu |  10 |       0 | 안녕요?ㅎ       | 2020-01-12 15:34:12 |
+----+--------+-----+---------+---------------+---------------------+
1 row in set (0.000 sec)

Update(수정)

테이블에 존재하는 데이터(행)을 수정하는 쿼리문: UPDATE table_name SET column_name=valuue WHERE condition

MariaDB [nodejs]> update nodejs.users SET comment='펭하!' WHERE id=1;
Query OK, 1 row affected (0.002 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Delete(삭제)

테이블에 존재하는 데이터(행)을 삭제하는 쿼리문: DELETE FROM table_name WHERE condition

MariaDB [nodejs]> DELETE FROM users WHERE id=1;
Query OK, 1 row affected (0.001 sec)

MariaDB 연동 연습을 위한 Express 프로젝트 생성

Express로 웹 서버 구축 후 이를 MariaDB와 연동하려 함

  1. Express 프로젝트 자동 생성을 위한 express-generator 설치: npm i -g express-generator
  2. FOLDER_NAME이라는 이름의 폴더에 Express 프로젝트 생성: express FOLDER_NAME --view=pug
➜  Nodejs express learn-sequelize --view=pug  # learn-sequelize 폴더에 Express 프로젝트 생성

# 대충 설치 과정.......
  1. FOLDER_NAME 폴더로 이동 후 npm i 명령어 실행
➜  Nodejs cd learn-sequelize  # learn-sequelize 폴더로 이동
➜  learn-sequelize npm i  # 프로젝트에 필요한 npm 패키지 설치
  1. npm start 명령어 실행 후 웹 브라우저로 http://localhost:3000 에 제대로 접속되는지 확인
➜  learn-sequelize npm start

> learn-sequelize@0.0.0 start /Users/ingyeo/Documents/Study/Nodejs/learn-sequelize
> node ./bin/www

Node.js – MariaDB 연동: 1. 쿼리문 직접 실행

https://www.smoh.kr/202 사이트를 참고하여 코딩함

  1. Express 프로젝트 폴더 경로에서 MariaDB Connector 설치: npm i mariadb
  2. MariaDB 접속 정보를 가진 pool 객체 및 커넥터 함수 구현
/*** models/mariaDBConn.js ***/

var mariadb = require('mariadb');

const pool = mariadb.createPool({
  host: '127.0.0.1', 
  port: 3306,
  user: 'root', 
  password: 'P@SsW0Rd!',
  connectionLimit: 5
});

async function getUserList() {
  let conn, rows;
  try {
    conn = await pool.getConnection();
    conn.query('USE nodejs'); // 사용할 DB 명시
    rows = await conn.query('SELECT * FROM users'); // 쿼리 실행
  }
  catch (err) { throw err; }
  finally {
    if (conn) conn.end();
    return rows;
  }
}

module.exports = { getUserList, }
/*** routes/index.js ***/

var mdbConn = require('../models/mariaDBConn');
var express = require('express');
var router = express.Router();

/* localhost:3000 주소로 접속 시 작동되는 라우터 */
router.get('/', function (req, res, next) {
  mdbConn.getUserList()
    .then((rows) => { res.json(rows) }) // 쿼리 결과가 JSON 형태로 출력됨
    .catch((err) => { console.error(err); });
});

module.exports = router;

Node.js – MariaDB 연동: 2. 시퀄라이즈(Sequelize) 사용

시퀄라이즈(Sequelize): Node에서 MariaDB 작업을 쉽게 도와주는 라이브러리

  • 자바스크립트 구문을 SQL로 변환
  • SQL을 쓰지 않아도 자바스크립트만으로 MariaDB를 조작할 수 있음

설치

  1. Express 프로젝트 폴더 경로에서 sequelize 및 mariadb 패키지 설치: npm i sequelize mariadb
  2. sequelize 커맨드 사용을 위한 sequelize 전역 설치: npm i -g sequelize-cli
  3. sequelize init
    • 이 명령어를 실행하면 config, models, migrations, seeders 폴더가 생성됨
  4. sequelize-cli에 의해 models 폴더 안에 index.js 파일이 자동 생성되나 그대로 사용 시 에러가 발생하고 필요없는 코드도 있으므로 다음과 같이 수정
/*** models/index.js ***/

const path = require('path');
const Sequelize = require('sequelize');

const env = process.env.NODE_ENV || 'development';
const config = require(path.join(__dirname, '..', 'config', 'config.json'))[env];
const db = {};

const sequelize = new Sequelize(config.database, config.username, config.password, config);

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

MariaDB 연결

app.js 파일에 시퀄라이즈 연동 코드 작성

/*** app.js ***/

// ...
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var sequelize = require('./models').sequelize; // sequelize require

var app = express();
sequelize.sync(); // 서버 실행 시 MariaDB와 연동
// ...

참고로 require('./models')require('./models/index.js')와 같음. 폴더 내의 index.js 파일은 require 시 이름을 생략할 수 있음.

모델 정의

  • MariaDB에서 정의한 테이블을 시퀄라이즈에서 정의
  • MariaDB의 테이블은 시퀄라이즈의 모델과 대응됨
    • 기본적으로 모델 이름은 단수형으로 사용
    • 테이블 이름은 복수형으로 사용

시퀄라이즈 모델을 정의하는 메서드: sequelize.define()

/*** models/user.js ***/

// 이전에 MariaDB에 users 테이블과 comments 테이블을 만들었으니 
// 시퀄라이즈에 User 모델과 Comment 모델 생성 및 연결
module.exports = (sequelize, DataTypes) => {
  return sequelize.define(
    /* 첫번째 인자: 테이블 이름 */
    'user', 

    /* 두번째 인자: 컬럼 모델 */
    {
    // 시퀄라이즈는 기본적으로 id를 기본키로 연결하므로 id 컬럼은 적을 필요가 없음
    name: {
      type: DataTypes.STRING(20), // VARCHAR -> STRING
      allowNull: false, // NOT NULL -> allowNull
      unique: true, // UNIQUE -> unique
    },
    age: {
      type: DataTypes.INTEGER.UNSIGNED, // INT -> INTEGER
      allowNull: false,
    },
    married: {
      type: DataTypes.BOOLEAN, // TINYINT -> BOOLEAN
      allowNull: false,
    },
    comment: {
      type: DataTypes.TEXT, // TEXT = TEXT
      allowNull: true,
    },
    created_at: {
      type: DataTypes.DATE, // DATETIME -> DATE
      allowNull: false,
      defaultValue: sequelize.literal('now()'),
    },
  }, 

  /* 세번째 인자: 테이블 옵션 */
  {
    timestamps: false, // true 시 시퀄라이즈는 자동으로 createdAt과 updateAt 컬럼 추가
  });
};
/*** models/comment.js ***/

module.exports = (sequelize, DataTypes) => {
  return sequelize.define('comment', {
    comment: {
      type: DataTypes.STRING(100),
      allowNull: false,
    },

    // commenter 컬럼에 대한 모델은 없음.
    // 여기에 정의해도 되긴 하지만 시퀄라이즈 자체에서 관계를 따로 정의할 수 있음.
    // 이것에 대해선 나중에...

    created_at: {
      type: DataTypes.DATE,
      allowNull: true,
      defaultValue: sequelize.literal('now()'),
    },
  }, {
    timestamps: false,
  });
};

시퀄라이즈의 자료형

MariaDB의 자료형과는 조금 다름

  • VARCHAR -> STRING
  • INT -> INTEGER
  • TINYINT -> BOOLEAN
  • DATETIME -> DATE
  • UNSIGNED가 적용된 INT -> INTEGER.UNSIGNED
  • ZEROFILL -> INTEGER.UNSIGNED.ZEROFILL

시퀄라이즈 옵션

옵션도 MariaDB와 조금 다름

  • NOT NULL -> allowNull
  • UNIQUE -> unique
  • DEFAULT -> defaultValue

주요 테이블 옵션

  • timestamps: 시퀄라이즈가 자동으로 createdAt과 updateAt 컬럼을 만들고 그 값도 자동 입력하도록 함
  • paranoid: 시퀄라이즈가 자동으로 deletedAt 컬럼을 만들고 그 값도 자동 입력하도록 함
    • 이 옵션은 timestamps 옵션이 true일 때 활성화됨
    • 데이터(행)을 삭제하는 명령을 내리면 실제로 삭제하는 대신 deletedAt에 제거된 날짜를 입력함
  • tableName: 시퀄라이즈가 생성하는 테이블 이름을 직접 설정할 때 사용
    • 시퀄라이즈는 define() 메서드의 첫번째 인자를 복수형으로 만들어 테이블 이름으로 사용함. 이런 자동 변환을 막을 때 사용.

모델 연결

models/index.js와 연결

/*** models/index.js ***/

const path = require('path');
const Sequelize = require('sequelize');

const env = process.env.NODE_ENV || 'development';
const config = require(path.join(__dirname, '..', 'config', 'config.json'))[env];
const db = {};

const sequelize = new Sequelize(config.database, config.username, config.password, config);

db.sequelize = sequelize;
db.Sequelize = Sequelize;

// db 객체에 User 및 Comment 모델을 담아둠
// db 객체를 require하여 User 및 Comment 모델에 접근할 수 있음.
db.User = require('./user')(sequelize, Sequelize);
db.Comment = require('./comment')(sequelize, Sequelize);

module.exports = db;

마지막으로 config 폴더 안의 config.json 파일의 development 속성을 다음과 같이 알맞게 수정

  • test와 production 속성은 각각 테스트와 배포 용으로 사용되므로 나중에 사용
  • development 속성에 대한 설정은 process.env.NODE_ENV가 development일 때 적용됨
    • 나중에 배포할 때는 이 값을 production으로 설정
    • 마찬가지로 테스트 환경에서는 이 값을 test로 설정
{
  "development": {
    "username": "root",
    "password": "P@sSw0rd!",
    "database": "nodejs",
    "host": "127.0.0.1",
    "dialect": "mariadb",
    "operatorsAliases": false
  },
  // ...
}

관계 정의

1:N 관계

comments 테이블의 외래키는 commenter 행이며 이는 user 테이블의 id 행을 참조함

  • 한 명의 유저는 여러 개의 댓글을 달 수 있고, 각각의 댓글에 작성자가 여러명일 수 없음
  • 따라서 users 테이블과 comments 테이블은 1:N 관계

시퀄라이즈에서 1:N 관계를 표현하는 메서드: hasMany()

  • 그 반대 방향을 표현하는 메서드: belongsTo()
/*** models/index.js ***/

// ...

db.sequelize = sequelize;
db.Sequelize = Sequelize;

db.User = require('./user')(sequelize, Sequelize);
db.Comment = require('./comment')(sequelize, Sequelize);

/* user -> comment: 1 -> N */
db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id'});
/* comment -> user: N -> 1 */
db.Comment.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id'});

// ...

1:1 관계

시퀄라이즈에서 1:1 관계를 표현하는 메서드: hasOne()

  • 그 반대방향을 표현하는 메서드 belongsTo()
    db.User.hasOne(db.Info, { foreignKey: 'user_id', sourceKey: 'id' });
    db.Info.belongsTo(db..User, { foreignKey: 'user_id', targetKey: 'id' });

N:M 관계

하나의 글엔 여러 개의 해시태그를 가질 수 있고 하나의 해시태그는 여러 개의 글에 포함될 수 있으므로 이는 N:M 관계

시퀄라이즈에서 N:M 관계를 표현하는 메서드: belongsToMany()

// through 속성: N:M 관계 특성 상 새롭게 생성될 모델의 이름을 명시
db.Post.belongsToMany(db.Hashtag, {through: 'PostHashtag' });
db.HashTag.belongsToMany(db.Post, {through: 'PostHashtag' });
// 이렇게 생성된 모델은 Post(id, content) <-> PostHashtag(postid, hashtagid) <-> Hashtag(id, title) 관계 테이블로서 정의됩니다.

시퀄라이즈 쿼리 생성

시퀄라이즈 쿼리는 SQL문을 자바스크립트로 생성하며 프로미스를 반환

생성

/* routes에 구현 */

const { User } = require('../models');

// INSERT INTO nodejs.users(name, age, married, comment) VALUES('pengsu', 10, 0, '자기소개1');
User.create({
  name: 'pengsu',
  age: 10,
  married: false, // MariaDB의 자료형이 아닌 시퀄라이즈 모델에 정의한 자료형을 넣어야 함
  comment: '자기소개1',
});

조회

// db.User, Sequelize.Op 객체 가져오기
const { User, Sequelize: { Op } } = require('../models');

// SELECT * FROM nodejs.users;
User.findAll({});

// SELECT * FROM nodejs.users LIMIT 1;
User.findAll({});

// SELECT name, married FROM nodejs.users;
User.FindAll({
  attributes: ['name', 'married'],
});

// SELECT name, age FROM nodejs.users WHERE married=1 AND age>30
User.findAll({
  attributes: ['name', 'age'],
  where: {
    married: 1,
    age: { [Op.gt]: 30 },
  },
});

// SELECT id, name FROM users WHERE married=0 OR age>30;
User.findAll({
  attributes: ['id', 'name'],
  where: {
    [Op.or]: [ { married: 0 }, { age: { [Op.gt]: 30 } } ],
  },
});

// SELECT id, name FROM users ORDER BY age DESC LIMIT 1 OFFSET 1;
User.findAll({
  attributes: ['id', 'name'],
  order: [['age', 'DESC']],
  limit: 1,
  offset: 1,
});
시퀄라이즈의 Op 연산자 종류
  • Op.gt: 초과(>)
  • Op.gte: 이상(>=)
  • Op.lt: 미만(<)
  • Op.lte: 이하(<=)
  • Op.ne: 같지 않음(!=)
  • Op.or: 또는(|)
  • Op.in: 포함
  • Op.notIn: 미포함

수정

const { User } = require('../models');

// UPDATE nodejs.users SET comment='바꿀 내용' WHERE id=2;
User.update({
  comment: '바꿀 내용',
}, {
  where: { id: 2 },
});

삭제

const { User } = require('../models');

// DELETE FROM nodejs.users WHERE id=2;
User.destroy({
  where: { id: 2 },
});

시퀄라이즈 쿼리 실행

단순히 DB 정보를 json 형태로 보여주는 코드만 간단하게 설명(프론트엔드는 구현하지 않음)

/*** routes/index.js ***/

var express = require('express');
var User = require('../models').User;

var router = express.Router();

router.get('/', function (req, res, next) {
  User.findAll()
    .then((users) => {
      res.json(users);
    })
    .catch((err) => {
      console.error(err);
      next(err);
    });
});

module.exports = router;
/*** routes/users.js ***/

var express = require('express');
var User = require('../models').User;

var router = express.Router();

router.get('/', function(req, res, next) {
  User.findAll()
    .then((users) => {
      res.json(users);
    })
    .catch((err) => {
      console.error(err);
      next(err);
    });
});

router.post('/', function(req, res, next) {
  User.create({
    name: req.body.name, //  req.body는 프론트엔드 측에서 받아온 정보
    age: req.body.age,
    married: req.body.married,
  })
    .then((result) => {
      console.log(result);
      res.status(201).json(result); // http status 201: Created
    })
    .catch((err) => {
      console.error(err);
      next(err);
    });
});

module.exports = router;
/*** routes/comments.js ***/

var express = require('express');
var { User, Comment } = require('../models');

var router = express.Router();

router.get('/:id', function(req, res, next) {
  Comment.findAll({
    include: {
      model: User,
      where: { id: req.params.id },
    },
  })
    .then((comments) => {
      console.log(comments);
      res.json(comments);
    })
    .catch((err) => {
      console.error(err);
      next(err);
    });
});

router.post('/', function(req, res, next) {
  Comment.create({
    commenter: req.body.id,
    comment: req.body.comment,
  })
    .then((result) => {
      console.log(result);
      res.status(201).json(result);
    })
    .catch((err) => {
      console.error(err);
      next(err);
    });
});

router.patch('/:id', function(req, res, next) {
  Comment.update({ comment: req.body.comment }, { where: { id: req.params.id } })
    .then((result) => {
      res.json(result);
    })
    .catch((err) => {
      console.error(err);
      next(err);
    });
});

router.delete('/:id', function(req, res, next) {
  Comment.destroy({ where: { id: req.params.id } })
    .then((result) => {
      res.json(result);
    })
    .catch((err) => {
      console.error(err);
      next(err);
    });
});

module.exports = router;

댓글 남기기