본문 바로가기

Node.js

Node.js - 시퀄라이즈 (Sequelize, SQL 데이터베이스 작업 라이브러리)

 

노드에서 MySQL 작업을 쉽게 할 수 있도록 도와주는 라이브러리가 있다. 바로 시퀄라이즈 (Sequelize) 이다.

 

시퀄라이즈는 ORM (Object-relational Mapping) 으로 분류되며, ORM은 자바스크립트 객체와 데이터베이스의 릴레이션을 매핑해주는 도구이다.

 

시퀄라이즈는 MySQL 외에도 MariaDB, PostgreSQL 등등 다른 데이터베이스에도 쓸 수 있다. 문법이 어느 정도 호환되므로 프로젝트를 다른 SQL 데이터베이스로 전환할 때도 편리하다.

 

시퀄라이즈를 쓰는 이유는 자바스크립트 구문을 알아서 SQL로 바꿔주기 때문이다. 따라서 SQL 언어를 직접 사용하지 않더라도 자바스크립트만으로 MySQL을 조작할 수 있고, 따라서 SQL 언어를 몰라도 MySQL을 어느 정도 다룰 수 있다 (물론 이것을 권장하진 않는다).

 

시퀄라이즈 프로젝트 생성

먼저 프로젝트를 생성해주자. npm init으로 노드 프로젝트를 시작해준다.

 

{
  "name": "learn-sequelize",
  "version": "0.0.1",
  "description": "Let's learn sequelize",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon app"
  },
  "author": "Effort",
  "license": "MIT"
}

 

이제 시퀄라이즈에 필요한 sequelize, sequelize-cli, mysql2 패키지를 설치한다. 또, 로그를 위한 morgan, 템플릿 뷰를 위한 nunjucks과 개발 모드로 nodemon을 설치해주자.

 

PS C:\Users\bumsu\nodejs-projects\노드js교과서\7\learn-sequelize> npm i express morgan nunjucks sequelize sequelize-cli mysql2

...

PS C:\Users\bumsu\nodejs-projects\노드js교과서\7\learn-sequelize> npm i -D nodemon

...

PS C:\Users\bumsu\nodejs-projects\노드js교과서\7\learn-sequelize> npx sequelize init

 

전역 설치 없이 명령어로 사용하기 위해 npx를 붙여 sequelize init을 호출해주었다. 이후에 폴더트리를 다시 보면 config, models, migrations, seeders 폴더가 생성된 것을 볼 수 있다. models 폴더 내의 index.js가 생성되었는지 확인한다.

 

sequelize-cli가 자동으로 생성해주는 코드는 그대로 사용할 때 에러가 발생하고, 필요없는 부분도 많으므로 다음과 같이 수정해준다.

 

const Sequelize = require('sequelize');

const env = process.env.NODE_ENV || 'development';
const config = require('../config/config')[env];
const db = {};

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

db.sequelize = sequelize;

module.exports = db;

 

config라는 변수에는 config/config.json 파일에 있는 설정값들이 담긴다. 데이터베이스 설정을 이 파일에서 불러온 후 new Sequelize를 통해 MySQL 연결 객체를 생성한다. 연결객체를 나중에 재사용하기 위해 db.sequelize에 넣어두었다.

 

MySQL 연결

이제 시퀄라이즈를 통해 익스프레스 앱과 MySQL을 연결해보자. app.js를 생성하여 익스프레스와 시퀄라이즈 연결코드를 다음과 같이 작성한다.

 

const express = require('express');
const path = require('path');
const morgan = require('morgan');
const nunjucks = require('nunjucks');

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

const app = express();
app.set('port', process.env.PORT || 3000);
app.set('view engine', 'html');
nunjucks.configure('views', {
    express: app,
    watch: true
});
sequelize.sync({ force: false })
    .then(() => {
        console.log('데이터베이스 연결됨.');
    }).catch((err) => {
        console.error(err);
    });

app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.use((req, res, next) => {
    const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
    error.status = 404;
    next(error);
});
app.use((err, req, res, next) => {
    res.locals.message = err.message;
    res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
    res.status(err.status || 500);
    res.render('error');
});

app.listen(app.get('port'), () => {
    console.log(app.get('port'), '번 포트에서 대기 중');
});

 

나머지는 다 봤던 부분이지만, sequelize.sync 부분은 처음 접하는 부분이다. sync 메서드를 사용하면 서버 실행 시 MySQL과 연동되도록 할 수 있다. 내부의 force: false 옵션이 되어있는데, 이를 true로 설정하면 서버 실행 시마다 테이블을 재생성한다. 테이블을 잘못 만든 경우에 true로 설정하면 된다.

 

이제 config.json에 정보를 넣어주자. 현 개발 환경은 development이니 development 안에 유저이름, 비밀번호, 데이터베이스명, 호스트 주소, 데이터베이스 (MySQL, SQLite 등) 종류를 넣어주자.

 

{
  "development": {
    "username": "root",
    "password": "........",
    "database": "nodejs",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  ...
}

 

참고로 operatorAliases 속성이 들어 있다면 삭제해주자.

 

위 코드의 models/index.js 파일에서 env 변수에 process.env.NODE_ENV 또는 'development'로 설정해 주었으며, 기본적으로 process.env.NODE_ENV가 기본적으로 'development'이기 때문에, development의 환경 설정을 가져오게 된다. 추후 배포할 때는 process.env.NODE_ENV를 production으로 설정하면 된다.

 

이제 npm start로 서버를 실행해보자.

 

PS C:\Users\bumsu\nodejs-projects\노드js교과서\7\learn-sequelize> npm start

> learn-sequelize@0.0.1 start C:\Users\bumsu\nodejs-projects\노드js교과서\7\learn-sequelize
> nodemon app

[nodemon] 2.0.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node app.js`
3000 번 포트에서 대기 중
Executing (default): SELECT 1+1 AS result
데이터베이스 연결됨.

 

위와 같은 메시지가 뜨면 성공이다. 연결에 실패한 경우 에러 메시지가 로깅된다. 에러는 주로 MySQL 데이터베이스를 실행하지 않았거나, 비밀번호를 틀렸거나, 설정 파일을 잘못 불러왔을 때 발생한다.

 

모델 정의하기

이제 MySQL에서 정의한 테이블을 시퀄라이즈에서도 정의해야 한다. MySQL의 테이블은 시퀄라이즈의 모델과 대응된다. 시퀄라이즈는 모델과 MySQL의 테이블을 연결해주는 역할을 한다. User 와 Comment 모델을 만들어 users 테이블과 comments 테이블에 연결해보자. 시퀄라이즈는 기본적으로 모델 이름은 단수형 (User), 테이블 이름은 복수형 (users) 으로 사용한다.

 

const Sequelize = require('sequelize');

class User extends Sequelize.Model {
    static init(sequelize) {
        return super.init({
            name: {
                type: Sequelize.STRING(20),
                allowNull: false,
                unique: true,
            },
            age: {
                type: Sequelize.INTEGER.UNSIGNED,
                allowNull: false,
            },
            married: {
                type: Sequelize.BOOLEAN,
                allowNull: false,
            },
            comment: {
                type: Sequelize.TEXT,
                allowNull: true,
            },
            created_at: {
                type: Sequelize.DATE,
                allowNull: false,
                defaultValue: Sequelize.NOW,
            },
        }, {
            sequelize,
            timestamps: false,
            underscored: false,
            modelName: 'User',
            tableName: 'users',
            paranoid: false,
            charset: 'utf8',
            collate: 'utf8_general_ci'
        });
    }
    static associate(db) {}
};

module.exports = User;

 

모델은 Sequelize.Model을 확장한 클래스로 선언한다. 모델은 static init 메서드와 static associate 메서드로 나뉘는데, init 메서드에서는 테이블에 대한 설정을 하고, associate 메서드에는 다른 모델과의 관계를 적는다. init 메서드의 super.init 메서드 첫 번째 인수는 테이블 컬럼에 대한 설정이고, 두 번째 인수는 테이블 자체에 대한 설정이다.

 

시퀄라이즈는 알아서 id를 기본 키로 연결하므로, id 컬럼은 따로 적어줄 필요는 없다. 나머지 컬럼의 스펙을 입력하자. 시퀄라이즈의 자료형은 MySQL과 조금 다르다. 아래 비교를 통해 맞는 옵션을 입력하자.

 

MySQL 시퀄라이즈
VARCHAR(100) STRING(100)
INT INTEGER
TINYINT BOOLEAN
DATETIME DATE
INT UNSIGNED INTEGER.UNSIGNED
NOT NULL allowNull: false
UNIQUE unique: true
DEFAULT now() defaultValue: Sequelize.NOW
ZEROFILL INTEGER.ZEROFILL

 

super.init의 두 번째 인수는 테이블 옵션이다.

 

  • sequelize: static init 메서드의 매개변수와 연결되는 옵션으로, db.sequelize 객체를 넣어야 한다. 나중에 model/index.js에서 연결한다.
  • timestamps: 현재 false로 설정되어 있다. 이 속성 값이 true면 시퀄라이즈는 createdAt과 updatedAt 컬럼을 추가하며, 각각 로우가 생성될 때와 수정될 때의 시간이 자동으로 입력된다. 그러나 예제에선 직접 created_at 컬럼을 만들었으므로 timestamps 속성이 필요없다.
  • underscored:  시퀄라이즈는 기본적으로 테이블명과 컬럼명을 카멜 표기법 (camel case) 으로 만든다. 이를 스네이크 표기법 (snake case) 으로 바꾸는 옵션이다 (예를들어 updatedAt 을 updated_at 으로).
  • modelName: 모델 이름을 설정할 수 있다. 노드 프로젝트에서 사용한다.
  • tableName: 실제 데이터베이스의 테이블 이름. 기본적으로 모델 이름의 소문자 및 복수형으로 만든다. 예를 들어 모델 이름이 User 라면 테이블 이름은 users 이다.
  • paranoid: true로 설정하면 deletedAt이라는 컬럼이 생긴다. 로우를 삭제할 때 완전히 지우지 않고, deletedAt에 지운 시각이 기록된다. 로우를 조회하는 명령을 내렸을 경우 deletedAt의 값이 null인 로우를 조회한다. 이렇게 하는 이유는 후에 로우를 복원하기 위해서다. 로우를 복원해야 할 상황이 생길 것 같다면 미리 true로 설정해두자.
  • charset, collate: 각각 utf8 과 utf8_general_ci 로 설정해야 한글이 입력된다. 이모티콘까지 입력할 수 있게 하고 싶다면 utf8mb4 와 utf8mb4_general_ci 를 입력한다.

Comment 모델도 다음과 같이 만들어주자.

 

const Sequelize = require('sequelize');

class Comment extends Sequelize.Model {
    static init(sequelize) {
        return super.init({
            comment: {
                type: Sequelize.STRING(100),
                allowNull: false,
            },
            created_at: {
                type: Sequelize.DATE,
                allowNull: true,
                defaultValue: Sequelize.NOW,
            },
        }, {
            sequelize,
            timestamps: false,
            modelName: 'Comment',
            tableName: 'comments',
            paranoid: false,
            charset: 'utf8mb4',
            collate: 'utf8mb4_general_ci'
        });
    }

    static associate(db) {}
};

module.exports = Comment;

 

근데 보면 commenter 컬럼이 없어졌다. 이 부분은 모델을 정의할 때 넣어도 되지만, 시퀄라이즈 자체에서 관계를 따로 정의할 수 있다. 이는 조금 뒤에 알아보자.

 

이제 models/index.js에 두 파일을 연결시켜주자.

 

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

const env = process.env.NODE_ENV || 'development';
const config = require('../config/config')[env];
const db = {};

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

db.sequelize = sequelize;

db.User = User;
db.Comment = Comment;

User.init(sequelize);
Comment.init(sequelize);

User.associate(db);
Comment.associate(db);

module.exports = db;

 

db라는 객체에 User과 Comment 모델을 담았다. 앞으로 db 객체를 require하여 User과 Comment 모델에 접근할 수 있을 것이다. 또한 User.init과 Comment.init은 각각의 모델의 static.init 메서드를 호출하며, init이 실행되어야 테이블이 모델로 연결된다. 다른 테이블과의 관계를 연결하는 associate 메서드 역시 미리 실행해둔다.

 

관계 정의하기

이번엔 users 테이블과 comments 테이블 간의 관계를 정의해보자. 사용자 한 명은 댓글을 여러 개 작성할 수 있지만, 댓글 하나에 작성자가 여러 명일 수는 없다. 이러한 관계를 일대다 (1:N) 관계라고 한다. 위 관계에서는 사용자가 1이고 댓글인 N이다.

 

다른 관계로는 일대일 (1:1), 다대다 (N:M) 관계가 있다. 일대일 관계로는 사용자와 사용자의 대한 정보 테이블을 예로 들 수 있으며, 다대다 관계로는 게시글 테이블과 해시태그 (#) 테이블 관계를 예로 들 수 있겠다. 한 게시글에 여러 해시태그가 달릴 수 있으며, 한 해시태그 또한 여러 게시글에 달릴 수 있다.

 

MySQL에서는 JOIN이라는 기능으로 여러 테이블 간의 관계를 파악해 결과를 도출한다. 시퀄라이즈는 JOIN 기능도 알아서 구현한다. 대신 테이블 간에 어떠한 관계가 있는지 시퀄라이즈에 알려줘야 한다.

 

1:N 관계 (hasMany, belongsTo)

시퀄라이즈에서는 1:N 관계를 hasMany 메서드로 표현한다. users 테이블의 로우 하나를 불러올 때 연결된 comments 테이블의 로우들도 같이 불러올 수 있다. 반대로 belongsTo 메서드도 있다. 이는 comments 테이블의 로우를 불러올 때 연결된 users 테이블의 로우를 가져온다.

 

이제 모델 각각의 static associate 메서드에 넣어주자. 각각 user.js 와 comment.js 이다.

 

    ...
    static associate(db) {
        db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id' });
    }
};

 

	...
    static associate(db) {
        db.Comment.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id' });
    }
};

 

간단히 User가 많은 댓글을 가질 수 있으니 User.hasMany가 되는 것이고, Comment는 한 User에 속할 수 있으니 Comment.belongsTo가 되는 것이다.

 

둘이 소통하는 키는 foreignKey인 commenter이며, User의 sourceKey는 곧 Commenter의 targetKey가 된다 (hasMany에서는 sourceKey, belongsTo에서는 targetKey). foreignKey를 따로 지정하지 않는다면 이름이 모델명+기본 키인 컬럼이 모델에 생성된다. 즉, 예를 들어 위 예제에서 commenter를 foreignKey로 직접 넣어주지 않았다면 모델명인 user과 기본 키인 id가 합쳐진 UserId가 foreignKey로 생성된다.

 

1:1 관계 (hasOne, belongsTo)

1:1 관계에서는 hasMany 대신 hasOne을 사용한다. foriegnKey, sourceKey, targetKey의 사용법은 1:N 관계와 같다.

 

1:1 관계라고 하더라도 belongsTo와 hasOne이 반대이면 안된다. belongsTo를 사용하는 Info 모델에 UserId 컬럼을 추가되기 때문이다.

 

N:M 관계 (belongsToMany)

시퀄라이즈에는 N:M 관계를 belongsToMany 메서드로 표현한다. 이 경우엔 어느 한 테이블이 어느 다른 테이블에 종속되는 관계가 아니다. 이 경우에, 예를 들어 Post 모델과 Hashtag 모델이 있다고 할 때, 다음과 같이 표현할 수 있다.

 

// Post
db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' });
// Hashtag
db.Hashtag.belongsToMany(db.Post, { through: 'PostHashtag' })

 

N:M 관계의 특성상 새로운 모델이 다음과 같이 생성되며, through 속성에 그 이름을 적으면 된다. 새로 생성된 PostHashtag 모델에는 게시글과 해시태그의 아이디가 저장된다.

 

N:M에서는 데이터를 조회할 때 여러 단계를 거쳐야 한다. 예를 들어 #노드 해시태그를 사용한 게시물을 조회하는 경우, 먼저 #노드 해시태그를 Hashtag 모델에서 조회하고, 가져온 태그의 아이디인 '1'을 바탕으로 PostHashtag 모델에서 hashtagId가 1인 postId들을 찾아 Post 모델에서 해당 정보를 가져와야 한다.

 

또한, 자동으로 만들어진 모델들도 다음과 같은 접근할 수 있다.

 

db.sequelize.models.PostHashtag

 

시퀄라이즈 쿼리

CRUD 작업을 하기 위해선 먼저 시퀄라이즈 쿼리를 알아야한다. SQL문을 자바스크립트로 생성하는 것이기 때문에, 시퀄라이즈의 방식을 사용해야 한다.

 

쿼리는 프로미스를 반환하므로, then을 붙여 결괏값을 받을 수 있다. 또는 async/await 문법과 함께 사용할 수도 있다.

 

로우 생성 쿼리 (Create)

먼저 로우를 생성하는 쿼리를 알아보자. 다음과 같은 시퀄라이즈 쿼리를 작성하면 로우를 생성할 수 있다. 모델을 불러와 create 메서드를 사용하여 로우를 생성한다.

 

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

User.create({
    name: 'beom seok',
    age: 23,
    married: false,
    comment: '안녕하세요.'
});

 

위 쿼리는 아래 SQL문과 같다.

 

INSERT INTO nodejs.users (name, age, married, comment) VALUES ('beom seok', 23, 0, '안녕하세요.');

 

주의해야할 점은 데이터를 넣을 때 MySQL의 자료형이 아닌 시퀄라이즈 모델에 정의한 자료형대로 넣어야한다. 가령 married의 경우 실제 MySQL에는 TINYINT로 되어 이전엔 0을 넣어주었었지만, 현재는 BOOLEAN으로 정의돼있으므로 false를 넣어주어야 한다. 사료형 또는 옵션에 부합하지 않는 데이터를 넣을 경우 시퀄라이즈가 에러를 발생시키며, 시퀄라이즈가 알아서 MySQL 자료형으로 바꿔주니 걱정말자.

 

로우 조회 쿼리 (Read)

findAll

모든 데이터를 조회하고 싶으면 findAll 메서드를 사용한다.

 

User.findAll({});

 

위 쿼리는 아래 SQL문과 같다.

 

SELECT * FROM nodejs.users;

 

findOne

테이블의 데이터를 하나만 가져온다. 

 

User.findOne({});

 

위 쿼리는 아래 SQL문과 같다.

 

SELECT * FROM nodejs.users LIMIT 1;

 

attributes, where

attributes 옵션을 사용하여 원하는 컬럼만 가져올 수도 있다. 또한, where 옵션으로 조건들을 나열할 수도 있다. where 옵션은 기본적으로 AND 옵션과 같다.

 

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

User.findAll({
    attributes: ['name', 'age'],
    where: {
        married: 1,
        age: { [Op.gt]: 30 },
    },
});

 

위 쿼리는 아래 SQL문과 같다.

 

SELECT name, age FROM nodejs.users WHERE married = 1 AND age > 30;

 

비교 구문이 조금 특이한데, 시퀄라이즈는 자바스크립트 객체를 사용하여 쿼리를 생성하기 때문에 특수한 연산자들이 사용된다.

 

자주 쓰이는 연산자로는 Op.gt (greater than, 초과), Op.gte (greater than or equal to, 이상), Op.lt (less than, 미만), Op.lte (less than or equal to, 이하), Op.ne (not equal, 같지 않음), Op.or (or, 또는), Op.in (in, 배열 요소 중 하나), Op.notIn (not in, 배열 요소와 모두 다름) 등이 있다.

 

Op.or은 다음과 같이 배열 내에 적용할 쿼리들을 나열하여 사용한다.

 

User.findAll({
    attributes: ['name', 'age'],
    where: {
        [Op.or]: [
            { married: 0 },
            { age: { [Op.gt]: 30 } }
        ],
    },
});

 

위 쿼리는 아래 SQL문과 같다.

 

SELECT name, age FROM nodejs.users WHERE married = 1 OR age > 30;

 

order

정렬은 order 옵션으로 처리한다. 배열 안에 배열이 있다는 점에 주의하자. 이는 정렬은 꼭 컬럼 하나가 아닌 두 개 이상으로도 할 수 있기 때문이다.

 

User.findAll({
    attributes: ['name', 'age'],
    order: [['age', 'DESC']]
});

 

위 쿼리는 아래 SQL문과 같다.

 

SELECT id, name FROM users ORDER BY age DESC;

 

limit, offset

조회할 로우 개수는 limit으로, 스킵할 로우 개수는 offset으로 할 수 있다.

 

User.findAll({
    attributes: ['name', 'age'],
    order: [['age', 'DESC']],
    limit: 1,
    offset: 1,
});

 

 

위 쿼리는 아래 SQL문과 같다.

 

SELECT id, name FROM users ORDER BY age DESC LIMIT 1 OFFSET 1;

 

 

limit이 1이라면 findAll 대신 findOne을 사용할 수 있다.

 

로우 수정 쿼리 (Update)

update 메서드로 수정할 수도 있다. 첫 번째 인수는 수정할 내용이고, 두 번째 인수는 어떤 로우를 수정할지에 대한 조건이다. where 옵션에 조건들을 적는다.

 

User.update({
    comment: '새로운 코멘트.',
}, {
    where: { id: 2 },
});

 

위 쿼리는 아래 SQL문과 같다.

 

UPDATE users SET comment = '새로운 코멘트.' WHERE id = 2;

 

로우 삭제 쿼리 (Delete)

로우 삭제는 destroy 메서드로 삭제한다. where 옵션에 조건을 적는다.

 

User.destroy({
    where: { id: 2 },
});

 

위 쿼리는 아래 SQL문과 같다.

 

DELETE FROM users WHERE id = 2;

 

관계 쿼리

findOne이나 findAll 메서드를 호출할 때 프로미스의 결과로 모델을 반환한다 (findAll은 모델의 배열을 반환한다).

 

const user = await Usser.findOne({});
console.log(user.name);

 

User 모델의 정보에도 바로 접근할 수 있겠지만, 더 편리한 점은 관계 쿼리를 지원한다는 것이다. MySQL로 따지면 JOIN 기능이다. 현재 User 모델은 Commenter 모델과 hasMany-belongsTo 관계가 맺어져 있으며, 만약 특정 사용자를 가져오면서 그 사람의 댓글까지 모두 가져오고 싶다면 include 속성을 사용한다.

 

const user = await Usser.findOne({
    include: [{
        model: Comment,
    }]
});
console.log(user.Comments);

 

관계가 있는 모델을 include 배열에 넣어주면 된다. 배열인 이유는 다양한 모델과 관계가 있을 수 있기 때문이다. 댓글은 여러 개일 수 있으므로 (hasMany) user.Comments로 접근 가능하다. 또는 다음과 같이 댓글에 접근할 수도 있다.

 

const user = await Usser.findOne({});
const comments = await user.getComments();
console.log(comments);

 

관계를 설정했다면, getComments (조회), setComments (수정), addComment (하나 생성), addComments (여러 개 생성), removeComments (삭제) 메서드를 지원한다. 동사 뒤에 모델의 이름이 붙는 형식으로 생성된다.

 

동사 뒤의 모델 이름을 바꾸고 싶다면 관계 설정 시 as 옵션을 사용한다 (아래는 user.js 모델 파일)

 

    ...
    static associate(db) {
        db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id', as: 'Answers' });
    }
};

 

위와 같이 관계를 설정했다면 댓글 객체도 user.Answers로 바뀌며, 쿼리 메서드들 또한 getAnswers 등으로 변한다.

 

include나 관계 쿼리 메서드에도 where나 attributes 같은 옵션들을 사용할 수 있다. 아래는 id가 1인 댓글만 가져오고, 그 중에서도 id 컬럼만 가져오도록 하고 있다.

 

const user = await Usser.findOne({
    include: [{
        model: Comment,
        where: {
            id: 1,
        },
        attributes: ['id'],
    }]
});

// 또는

const comments = await user.getComments({
    where: {
    	id: 1,
    },
    attributes: ['id'],
});

 

조회는 위와 같이 하지만, 수정, 생성, 삭제 때는 조금 다른 점이 있다.

 

// 생성
const user = await Usser.findOne({});
const comment = await Comment.create();
await user.addComment(comment);
// 또는
await user.addComment(comment.id);

// 여러 개 생성
const comment1 = await Comment.create();
const comment2 = await Comment.create();
await user.addComment([comment1, comment2]);

 

관계 쿼리 메서드의 인수로 추가할 댓글 모델을 넣거나 댓글의 아이디를 넣으면 된다. 수정이나 삭제 또한 마찬가지이다.

 

SQL 쿼리 그대로 사용하기

시퀄라이즈의 쿼리를 사용하기 싫거나 헷갈린다면 직접 SQL문을 통해 쿼리할 수도 있다.

 

const [result, metadata] = await sequelize.query('SELECT * FROM comments');

 

쿼리 수행하기

이제까지 배운 쿼리로 CRUD 작업을 해보자. views 폴더 내에 sequelize.html, error.html 파일을 만들고 작성하자. 만들기 귀찮으면 https://github.com/ZeroCho/nodejs-book/tree/master/ch7/7.6/learn-sequelize 여기서 가져오도록 하자. 프론트 엔드 코드는 그리 중요하지 않으니 (현재) views/sequelize.html, views/error.html, public/sequelize.js 는 위 레포지토리에서 가져왔다.

 

미리 라우터들을 app.js에 연결해주자.

 

...

const indexRouter = require('./routes');
const usersRouter = require('./routes/users');
const commentsRouter = require('./routes/comments');

...

app.use(express.urlencoded({ extended: false }));

app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/comments', commentsRouter);

...

 

이제 라우터를 만들어주자. routes 폴더를 만들어 index.js를 만들어준다.

 

const express = require('express');
const User = require('../models/user');

const router = express.Router();

router.get('/', async (req, res, next) => {
    try {
        const users = await User.findAll();
        res.render('sequelize', { users });
    } catch (err) {
        console.error(err);
        next(err);
    }
});

module.exports = router;

 

위 index.js 파일은 라우터로 / 경로에 진입했을 때 자동으로 일단 users 데이터를 가져오도록 한다. 그 후, 해당 데이터를 sequelize.html를 렌더링 할 때 넘겨준다.

 

아래는 각각 users.js와 comments.js 이다.

 

const express = require('express');
const User = require('../models/user');
const Comment = require('../models/comment');

const router = express.Router();

router.route('/')
    .get(async (req, res, next) => {
        try {
            const users = await User.findAll();
            res.json(users);
        } catch (err) {
            console.error(err);
            next(err);
        }
    })
    .post(async (req, res, next) => {
        try {
            const user = await User.create({
                // 요청의 body에 parameter들이 담겨있음
                name: req.body.name,
                age: req.body.age,
                married: req.body.married,
            });
            console.log(user);
            res.status(201).json(user);
        } catch (err) {
            console.error(err);
            next(err);
        }
    });

router.get('/:id/comments', async (req, res, next) => {
    try {
        const comments = await Comment.findAll({
            include: {
                model: User,
                where: { id: req.params.id }, // 요청에서 라우트로 들어오는 id값
            },
        });
        console.log(comments);
        res.json(comments);
    } catch (err) {
        console.error(err);
        next(err);
    }
});

module.exports = router;

 

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

const router = express.Router();

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

router.route('/:id')
    .patch(async (req, res, next) => {
        try {
            const result = await Comment.update({
                comment: req.body.comment
            }, {
                where: { id: req.params.id }
            });
            res.json(result);
        } catch (err) {
            console.error(err);
            next(err);
        }
    })
    .delete(async (req, res, next) => {
        try {
            const result = await Comment.destroy({ where: { id: req.params.id } });
            res.json(result);
        } catch (err) {
            console.error(err);
            next(err);
        }
    });

module.exports = router;

 

이제 npm start로 서버를 실행시켜주고, localhost:3000으로 접속하면, 

 

이와 같은 페이지가 잘 나타난다. 댓글을 등록하면 아래와 같이 댓글이 생성된다.

모든 기능이 잘 작동한다.

 

 

출처

Node.js 교과서 개정 2판 - 길벗, 조현영