Node.js

[Node.js] #7 MySQL

lgvv 2023. 7. 23. 15:31

MySQL

 

모든 데이터를 변수에 저장했다는 것은 메모리에 저장한다는 것.

메모리는 휘발성이라 종료하면 사라짐.

영구저장을 위해서는 데베를 사용해야 한다.

다양한 DB가 있지만 MySQL과 몽고디비 두가지를 이용할 예정.

 

# mysql 설치

brew install mysql
brew services start mysql
mysql_secure_installation

 

# 워크벤치 설치

콘솔로는 데이터를 한눈에 보기에 무리가 있으므로 워크벤치라는 프로그램을 사용하면 데베 내부에 저장된 데이터를 시각적으로 확인할 수 있어서 편리함.

 

 

# 테이블 생성하기

대부분 아는거라 대충 나만 알아보도록 정리

 

VARCHAR(자릿수): 가변길이 

CHAR(자릿수): 고정길이

 - 둘다 자릿수가 10이라고 하면, VARCHAR는 0~10 CHAR는 부족한 공간을 스페이스로 채워 넣음.

TEXT: 긴 글을 사용할 때 사용

 - VARCHAR는 수백 자 이내의 문자열에 사용.

TINYINT: -128부터 127까지의 정수를 저장할 때 사용. 1또는 0만 저장한다면 boolean같은 역할.

DATETIME은 날짜와 시간에 대한 정보를 담는다.

AUTO_INCREMENT: 생성될 때마다 자동으로 숫자 1 올림.

ZEROFILL: 숫자의 자릿수가 고정되어 있을 때 사용되어 있음. ZEROFILL을 하면 숫자가 비었을 때 모두 0을 넣는다

 - INT(4)인데 숫자 1을 넣었다면 0001이 된다.

DEFAULT: MySQL 기본값을 넣는다.

해당 컬럼이 기본키인 경우에 PRIMARY KEY 옵션을 설정

UNIQUE_INDEX: 고유해야 하는지에 대한 옵션이며, PRIMARY KEY나 UNIQUE INDEX의 경우에는 데이터베이스가 별도로 컬럼을 관리하므로 조회 시 속도가 빨라진다. 기본키도 사실 고유해야 하지만 UNIQUE_INDEX를 자동으로 포함하므로 따로 적지 않아도 된다.

COMMENT 테이블에 대한 보충 설명을 의미 테이블이 무슨 역할을 하는지 적어두며 되고, 필수는 아니다.

ENGINE은 여러가지 의미가 있지만 MyISAM과 InnoDB 제일 많이 사용된다. 이번에는 InnoDB를 엔진으로 사용.

 

워크벤치라는 툴을 쓸건데, MySQL을 직접 작성할 수도 있지만, 일단 노드js 어떻게 쓰는지가 초점이라 웨크벤치 툴로 뚝딱뚝딱 만들어보자.

 

워크벤치 이미지

 

PK: 프라이머리 키 여부

NN: 빈칸을 허용할지 여부

UQ(유니크 인덱스)

UN은 Unsigned, ZF는 제로 필

Default/Expression은 기본값을 설정해두는것

 

코멘트테이블

Foreign Keys에서 CASCADE를 해두면 다른쪽에서 지우거나 업데이트하면 코멘트 테이블에도 반영된다.

 

# CRUD 생성하기

Create, Read, Update, Delete의 약자로.

 

페이지 네이션 구현에 필요한 OFFSET만 보자.

OFFSET [건너뛸 숫자]

처음 1~20을 조회하면 다음은 21에서 40까지 조회한다

mysql> 
SELECT id, name FROM nodejs.users
ORDER BY age DESC
LIMIT 1 
OFFSET 1;

 

update

UPDATE nodejs.users
SET comment = '바꿀 내용' WHERE id = 2;

 

delete

DELETE FROM nodejs.users WHERE id = 2'

 

# 시퀄라이즈 사용하기

 

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

시퀄라이즈를 단지 MySQL과 같이써야하는건 아니다. MariaDB, PostgreSQL, SQLite, MSSQL 등 다른 데베랑도 같이 사용 가능하다.시퀄라이즈 사용하는 이유는 SQL언어를 직접 사용안해도 자스로 바꿔주기 때문.

 

npm i express morgan nunjucks sequelize sequelize-cli mysql2
npm i -D nodemon

sequelize-cli는 시퀄라이즈 명령어를 실행하기 위한 패키지이므로 mysql2는 MySQL과 시퀄라이즈를 이어주는 드라이버이다.

mysql2 자체는 데이터베이스 프로그램이 아니므로 오해하면 안 된다.

 

설치 완료 후 sequelize init 명령어를 호출하면 안된다. 전역 설치 없이 명령어로 사용하려면 앞에 npx를 붙이면 된다.

npx sequelize init

config, models, migrations, seeders 폴더가 생성된다. models 폴더 안의 index.js가 생성되었는지 확인 sequelize-cli가 자동으로 생성되는 코드는 그대로 사용할 때 에러가 발생하고 필요없는 부분도 많아서 수정해 주어야 한다.

 

이건 뒤에 나오므로 그때 index.js 파일을 보자

MySQL에 정의한 거 시퀄라이즈에도 정의해 주어야 한다.

 

 

const Sequelize = require('sequelize');

module.exports = 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, // ✅ static initiate 메서드의 매개변수와 연결되는 옵션으로 db.sequelize 객체를 넣어야 한다.
      timestamps: false, // ✅ true 시퀄라이즈가 자동으로 create_at과 update_at, timestamps 속성이 필요 없다,
      underscored: false, // ✅ 시퀄라이즈 기본적으로 테이블명과 칼럼명을 캐멀 케이스로 만든다. 이를 스네이크 케이스로 만드는 거다.
      modelName: 'User', // ✅ 모델 이름
      tableName: 'users', // ✅ 보통 소문자 및 복수형
      paranoid: false, // ✅ true면 deletedAt이라는 칼럼 생김 로우를 삭제할 떄 완전히 지워지지 않고 지운 시각 기록. row 조회시 null인 것만 조회된다. 나중에 row 복원해야 할 때.
      charset: 'utf8', // ✅ 한글입력 위해서
      collate: 'utf8_general_ci', // ✅ 한글입력 위해서
    });
  }

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

 

User 모델을 만들고 모듈로 exports했다. User 모델은 Sequelize.Model을 확장한 클래스로 선언한다.

클래스 문법을 사용하지만 클래스에 대한 지식이 없어도 사용할 수 없다.

 

static initate 메서드와 static associate 메서드로 나뉘어 집니다.

 

모델.init 메서드의 첫 번째 인수가 테이블 칼럼에 대한 설정이고, 두 번째 인수가 테이블 자체에 대한 설정

시퀄라이즈는 알아서 id를 기본 키로 연결하므로 id 컬럼은 적어줄 필요가 없다

나머지 컬럼의 스펙을 입력한다. MySQL의 자료형과는 다를 수 밖에 없다.

 

단, 시퀄라이즈의 자료형은 MySQL의 자료형과는 다르다. 시퀄라이즈는 MySQL 이외에 다른 데베도 처리할 수 있어야 하므로 MySQL의 자료형과는 다를 수 밖에 없다.

 

VARCHAR는 STRING으로

INT는 INTEGER로

TINYINT는 BOOLEAN으로

DATETIME은 DATE로 적는다.

 

INTEGER.UNSIGNED는 UNSIGNED 옵션이 적용된 INT를 의미한다. 여기에 ZEROFILL 옵션도 사용하고 싶다면 INTEGER.UNSIGNED.ZEROFILL을 적는다.

 

allowNull은 NOT NULL 옵션과 동일하다.

unique는 UNIQUE 옵션이다.

defaultValue는 기본값(default)를 의미한다. Squelize.NOW로 현재 시간을 기본값을 사용할 수 있고 SQL now()와 같다.

 

const Sequelize = require('sequelize');

module.exports = 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) {
    db.Comment.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id' });
  }
};

users 테이블과 연결된 commenter 칼럼이 없다. 이 부분은 모델을 정의할 떄 넣어도 되지만 시퀄라이즈 자체에서 관계를 따로 정의할 수 있다. 이에 대해서는 뒤에서 알아보자.

 

const Sequelize = require('sequelize');
const User = require('./user'); // ✅ User와 Comment 모델을 담아두었다.
const Comment = require('./comment'); // ✅ User와 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.Sequelize = Sequelize;

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

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

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

module.exports = db;

 

initiate 메서드는 각각의 메서드를 호출한다. 모델.init이 실행되어야 테이블이 모델로 연결됩니다.

다른 테이블과 관계를 연결하는 static associate 메서드도 미리 실행해둔다.

 

# 관계 정의하기

 

users와 comments 테이블 간의 관계를 정의해보자.

해시태그 같은 경우는 N: M 관계

 

1:N 관계

User -- hasMany --> Comment

User <-- belongsTo -- Comment

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

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

왜 db라는 매개변수를 사용하냐면 최상단에 const Comment = require('./comment')식으로 불러오면 안될까 생각하나 이 경우에는 순환참조가 발생함.

 

index.js에서 각 모델을 불러와 db로 불러와 매개변수로 넘기는 방식을 취함.

foreignKey로 '모델명+기본 키'의 컬럼이 모델에 생성됩니다. 예를들어 commenter를 외래 키로 넣어주지 않았다면 user(모델명) +기본 키(id)가 합쳐진 userId가 외래키로 생성됩니다.

 

1:1

hasOne 사용. 

User -- hasOne --> Info

User <-- belongsTo -- Info

 

static associate(db) {
    db.User.hasOne(db.Info, { foreignKey: 'UserId', sourceKey: 'id' });
}

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

 

N: M

N:M관계

 

static associate(db) {
    db.Post.belongsToMany(db.HashTag, , { through: 'PostHashTag' });
}

static associate(db) {
    db.HashTag.belongsToMany(db.Post, { through: 'PostHashTag' });
}

 

양쪽 모델 모둘에 belongsToMany 메서드를 사용하면 된다.

PostHashTag 모델에는 게시글과 해시태그의 아이디가 저장된다.

 

N: M 관계 테이블에서는 조회할 때 여러 단계를 거쳐야 함.

#노드 해시태그를 사용한 게시물을 조회하는 경우 생각해보자.

먼저 #노드 해시태그를 Hashtag 모델 조회하고 가져온 태그의 아이디(1)을 바탕으로 PostHashtag 모델에서 hastagId가 1인 postId들을 찾아 Post 모델에서 가져온다.

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

db.sequelize.models.PostHashtag

 

N: M

 

 

# 쿼리 알아보기

시퀄라이즈 CRUD 작업하려면 먼저 시퀄라이즈 쿼리를 알아야 한다. SQL문을 자바스크립트로 생성하는 것.

SQL문에 상응하는 옵션들이 있음. 쿼리는 프로미스를 반환하므로 then을 붙여 결괏값을 받을 수 있다. async/await 문법과 같이 사용할 수 있다.

 

로우를 생성하는 쿼리부터 알아보자.

 

INSERT INTO nodejs.users (name, age, married, comment) VALUES ('zero', 24, 0, '자개소개1');
const { User } = require('../models');
User.create({
	name: 'zero',
    age: 24,
    married: false,
    comment: '자기소개1'
})

models 모듈에서 User 모델을 불러와 create 메서드를 사용하면 됩니다.

앞으로는 User 모델을 불러왔다는 전제하에 소개합니다.

 

시퀄라이즈 모델에 MySQL이 아니라 정의한 자료형대로 넣어야 한다.

이것이 married가 0이 아니라 false인 이유.

 

자료형이나 옵션에 부합하지 않는 데이터를 넣었을 때는 시퀄라이즈가 에러를 발생시킨다.

 

 

다음은 users 테이블의 모든 데이터를 조회하는 SQL문이다. 

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

 

 

다음은 하나만 가져오는거다.

 

 

 

SELECT * FROM nodejs.users LIMIT 1;
User.findOne({});​

 

attributes 옵션 사용해서 원하는 컬럼만 가져올 수 있다.

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

 

where 옵션이 조건들을 나열하는 옵션

SELECT name, age FROM nodejs.users WHERE marreid = 1 AND age > 30;
const { Op } = require('sequelize');
const { User } = require('../models');

User.findAll({
	attributes: ['name', 'age'],
    where: { 
    	married: true,
        age: { [Op.gt]: 30 },  // ES2015 문법이다. 특수 연산자인데 시퀄라이즈 내부의 Op객체를 가져온다.
    },
});

Op.gt(초과), Op.gte(이상), Op.1t(미만), Op.1te(이하), Op.ne(같지 않음), Op.or(또는), Op.in(배열 요소 중 하나), Op.notIn(배열 요소와 모두 다름) 등

 

SELECT id, name FROM users ORDER BY age DESC;
User.findAll({
	attrigutes: ['id', 'name'],
    order: [['age', 'DESC']],
    limit: 1,
});

시퀄라이즈의 정렬 방식임. 정렬은 꼭 칼럼 하나로만 하는게 아님. 제한도 할 수 있다.

 

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,
});

리밋말고 오프셋으로도 구현 가능

 

 

이번에는 로우를 수정하는 쿼리.

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

메서드를 이렇게 수정할 수도 있다.

 

이번에는 삭제해보자. 조건 where에 걸어서 삭제 가능

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

 

 

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

const user = await User.findOne({});

console.log(user.nick); // 사용자 닉네임.

 

User 모델의 정보에도 바로 접근할 수 있지만 더 편리한 점은 관계 쿼리를 지원한다는 것이다.

MySQL로 따지면 JOIN 기능이다. 만약 특정 사용자를 가져오면서 그 사람의 댓글까지 모두 가져오고 싶다면?

include사용해서 가져온다.

const user = await User.findOne({
	include: [{
    	model: Comment,
    }]
});
console.log(user.Comments); // 사용자 댓글

 

어떤 모델과 관계가 있는지를 include 배열에 넣어주면 된다.

배열인 이유는 다양한 모델과 관계가 있을 수 있기 때문이다. 댓글은 여러 개일 수 있으므로

const user = await User.findOne{()};
const comments = await user.getComments();
console.log(comments); // 사용자 댓글

 

관계를 설정했다면 getComments(조회) 외에도 setComments(수정) addComment(하나 생성), addComments(여러 개 생성), removeComments(삭제) 메서드를 지원

동사 뒤에 모델이 붙는 형식

 

// 관계 설정할 때 as로 등록
db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id', as: 'Answer'});


// 쿼리할 때는
const user = await User.findOne{()};
const comments = await user.getAnswers();
console.log(comments); // 사용자 댓글

 

as를 설정하면 include할 때 추가되는 댓글 객체도 user.Answers로 바뀝니다.

include나 관계 쿼리 메서드에도 where이나 attributes 같은 옵션을 사용할 수 있다.

 

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

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

댓글을 가져올 때는 id가 1인 댓글만 가져오고, 컬럼도 id 컬럼만 가져오도록 하고 있다.

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

 

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

// ✅ 여러개 추가시 배열로 가능

const user = await User.findOne({});
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 commnets');
console.log(result);

웬만하면 시퀄라이즈의 쿼리를 사용하는거 추천하지만 시퀄라이즈 쿼리로 할 수 없는 경우에는 위와 같이 하면 된다.

 

# 쿼리 수행하기

모델에서 데이터를 받아 페이지를 렌더링하는 방법과 JOSN 형식으로 데이터를 가져오는 방법과 JSON 형식으로 가져오는 방법을 알아보자.

 

 

간단하게 사용자 정보를 등록하고 사용자가 등록한 댓글을 사용하는 방법

// 사용자 이름 눌렀을 때 댓글 로딩
document.querySelectorAll('#user-list tr').forEach((el) => {
  el.addEventListener('click', function () {
    const id = el.querySelector('td').textContent;
    getComment(id);
  });
});
// 사용자 로딩
async function getUser() {
  try {
    const res = await axios.get('/users');
    const users = res.data;
    console.log(users);
    const tbody = document.querySelector('#user-list tbody');
    tbody.innerHTML = '';
    users.map(function (user) {
      const row = document.createElement('tr');
      row.addEventListener('click', () => {
        getComment(user.id);
      });
      // 로우 셀 추가
      let td = document.createElement('td');
      td.textContent = user.id;
      row.appendChild(td);
      td = document.createElement('td');
      td.textContent = user.name;
      row.appendChild(td);
      td = document.createElement('td');
      td.textContent = user.age;
      row.appendChild(td);
      td = document.createElement('td');
      td.textContent = user.married ? '기혼' : '미혼';
      row.appendChild(td);
      tbody.appendChild(row);
    });
  } catch (err) {
    console.error(err);
  }
}
// 댓글 로딩
async function getComment(id) {
  try {
    const res = await axios.get(`/users/${id}/comments`);
    const comments = res.data;
    const tbody = document.querySelector('#comment-list tbody');
    tbody.innerHTML = '';
    comments.map(function (comment) {
      // 로우 셀 추가
      const row = document.createElement('tr');
      let td = document.createElement('td');
      td.textContent = comment.id;
      row.appendChild(td);
      td = document.createElement('td');
      td.textContent = comment.User.name;
      row.appendChild(td);
      td = document.createElement('td');
      td.textContent = comment.comment;
      row.appendChild(td);
      const edit = document.createElement('button');
      edit.textContent = '수정';
      edit.addEventListener('click', async () => { // 수정 클릭 시
        const newComment = prompt('바꿀 내용을 입력하세요');
        if (!newComment) {
          return alert('내용을 반드시 입력하셔야 합니다');
        }
        try {
          await axios.patch(`/comments/${comment.id}`, { comment: newComment });
          getComment(id);
        } catch (err) {
          console.error(err);
        }
      });
      const remove = document.createElement('button');
      remove.textContent = '삭제';
      remove.addEventListener('click', async () => { // 삭제 클릭 시
        try {
          await axios.delete(`/comments/${comment.id}`);
          getComment(id);
        } catch (err) {
          console.error(err);
        }
      });
      // 버튼 추가
      td = document.createElement('td');
      td.appendChild(edit);
      row.appendChild(td);
      td = document.createElement('td');
      td.appendChild(remove);
      row.appendChild(td);
      tbody.appendChild(row);
    });
  } catch (err) {
    console.error(err);
  }
}
// 사용자 등록 시
document.getElementById('user-form').addEventListener('submit', async (e) => {
  e.preventDefault();
  const name = e.target.username.value;
  const age = e.target.age.value;
  const married = e.target.married.checked;
  if (!name) {
    return alert('이름을 입력하세요');
  }
  if (!age) {
    return alert('나이를 입력하세요');
  }
  try {
    await axios.post('/users', { name, age, married });
    getUser();
  } catch (err) {
    console.error(err);
  }
  e.target.username.value = '';
  e.target.age.value = '';
  e.target.married.checked = false;
});
// 댓글 등록 시
document.getElementById('comment-form').addEventListener('submit', async (e) => {
  e.preventDefault();
  const id = e.target.userid.value;
  const comment = e.target.comment.value;
  if (!id) {
    return alert('아이디를 입력하세요');
  }
  if (!comment) {
    return alert('댓글을 입력하세요');
  }
  try {
    await axios.post('/comments', { id, comment });
    getComment(id);
  } catch (err) {
    console.error(err);
  }
  e.target.userid.value = '';
  e.target.comment.value = '';
});

 

와 어렵다! 일단 훅 읽고 넘어가보자

 

 

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

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

const { sequelize } = require('./models');
const indexRouter = require('./routes');
const usersRouter = require('./routes/users');
const commentsRouter = require('./routes/comments');

const app = express();
app.set('port', process.env.PORT || 3001);
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('/', indexRouter);
app.use('/users', usersRouter);
app.use('/comments', commentsRouter);

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'), '번 포트에서 대기 중');
});

 

 

라우터의 내용은 sequlize.js에 나오는 GET, POST, PUT, DELETE 요청에 해당하는 라우터를 만든다. routes 폴더를 만들고 그 안에 index.js를 작성하면 된다.

 

// 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;

먼저 GET /로 접속했을 때의 라우터입니다. User.findAll 메서드로 모든 사용자를 찾은 후 sequelize.html을 렌더링할 때 결과값인 uses를 넣습니다. 시퀄라이즈는 프로미스를 기본적으로 지원하므로 async/await과 try/catch문을 사용해서 각각 조회 성공시와 실패시의 정보를 얻을 수 있다

 

다음은 user.js다. router.route('/')로 같은 경로는 하나로 묶었다.

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({
        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 },
      },
    });
    console.log(comments);
    res.json(comments);
  } catch (err) {
    console.error(err);
    next(err);
  }
});

module.exports = router;

GET /users와 POST /users 주소로 요청이 들어올 때의 라우터이다.

각각 사용자를 조회하는 요청과 등록하는 요청을 처리한다. GET /에서도 사용자 데이터를 조회했지만, GET /users에서는 데이터를 JSON 형식으로 반환한다는 것에 차이가 있다.

 

GET /users/id:/comments 라우터에는 findAll 메서드에 추가되어 있다.

include 옵션에서는 model 속성에는 User 모델을, where 속성에는 :id로 받은 아이디 값을 넣는다.

:id는 라우트 매개변수는 req.params.id로 값을 가져올 수 있다.

GET /users/1/comments라면 사용자 id가 1인 댓글을 불러오자. 조회된 댓글 객체에는 include로 넣어준 사용자 정보도 들어있어서 작성자의 이름이나 나이 등도 조회 가능하다.

 

다음은 comment.js

const express = require('express');
const { 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;

 

댓글에 CRUD 작업을 하는 라우터다. POST /comments, PATCH /comments/:id, DELETE/comments/:id를 등록

 

이제 npm start로 서버를 실행하고 시퀄라이즈가 수행하는 SQL문이 나오므로 어떤 동작을 하는지 확인할 수 있다.

SQL 구문이 보고싶지 않다면 config/config.json의 dialect 속성 ㅣㅌ에 "logging": false를 추가하면 된다.

 

접속 시 GET / 라우터에서 User.findAll 메서드를 호출하므로 그에 따른 SQL문이 실행되는 모습이다.