* 위 내용을 정리하였음
7.6 시퀄라이즈 사용하기
- 노드에서 MySQL 데이터 베이스를 작업하기 쉽게 해주는 라이브러리 이다.
- ORM(Object-relational Mapping)이라고 한다. 자바스크립트 객체와 데이터베이스의 relation을 매핑해주는 도구이다.
- 즉 자바스크립트 구문을을 알아서 SQL로 바꿔준다.
$ npm i sequelize sequelize-cli mysql2
- sequelize-cli : 시퀄라이즈 명령어를 실행하기 위한 패키지
- mysql2 : MySQL과 시퀄라이즈를 이어주는 드라이버
- 설치 완료 후 sequelize init 명령어를 호출한다. 전역 설치 없이 명령어로 사용하려고 앞에 npx를 붙였다.
$ npx sequelize init
Sequelize CLI [Node: 14.0.0, CLI: 6.1.0, ORM: 6.2.3]
Created "config\config.json"
Successfully created models folder at ...
Successfully created migrations folder at ...
Successfully created seeders folder at ...
- config, models, migrations, seeders 폴더가 생성되었다.
- models 폴더 안에 index.js를 다음과 같이 수정한다.
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;
- Sequelize는 시퀄라이즈 패키지이자 생성자이다. config/config.json에서 데이터베이스 설정을 불러온 후 new Sequelize를 통해 MySQL 연결 객체를 생성한다.
- config설정들은 process.env.NODE_ENV가 development일 때 적용된다.
- 나중에 배포할 때 process.env.NODE_ENV를 production으로 설정한다.
- 즉 배포 환경을 위해 데이터베이스를 설정할 때는 config/config.json의 production 속성을 수정하면 된다.
- 연결 객체를 나중에 재사용하기 위해 db.sequelize에 넣어두었다.
7.6.1 MySQL 연결하기
- app.js를 만들고 아래 코드를 작성한다.
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const nunjucks = require('nunjucks');
const { sequelize } = require('./models');
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((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를 불러올때 require('./models')는 require('./models/index.js')와 동일하다. (index.js는 생략 가능)
- db.sequelize를 불러와서 sync 메서드를 사용해 서버 실행 시 MySQL과 연동되도록 한다. force: false 옵션을 true로 설정하면 서버 실행 시마다 테이블을 재생성한다.
- MySQL과 연동할 때는 config/config.json 정보가 사용된다. operatorAliases 속성이 들어 있다면 삭제한다.
{
"development": {
"username": "root",
"password": "[root 비밀번호]",
"database": "nodejs",
"host": "127.0.0.1",
"dialect": "mysql"
},
...
}
- development.password와 development.database를 현재 MySQL 커넥션과 일치하게 수정한다.
- test와 production은 각각 테스트 용도와 배포 용도로 접속하기 위해 사용되는 것이다.
- npm start로 앱을 실행해보자.
7.6.2 모델 정의하기
- MySQL로 정의한 테이블들을 sequelize에도 정의해준다.
- 서로 대응되도록 정의한 다음 MySQL의 테이블들을 연결한다.
- models/user.js파일을 만들어 아래 코드를 작성한다.
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,
timestamps: false,
underscored: false,
modelName: 'User',
tableName: 'users',
paranoid: false,
charset: 'utf8',
collate: 'utf8_general_ci',
});
}
static associate(db) {
db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id' });
}
};
- User 모델은 Sequelize.Model을 확장한 클래스이다.
- 모델은 크게 static init 메서드와 static associate 메서드로 나뉜다.
- static init 메서드에는 테이블에 대한 설정을 한다.
- static associate 메서드에는 다른 모델과의 관계를 적는다.
- init 메서드에서 super.init 메서드의 첫 번째 인수가 테이블 컬럼에 대한 설정이고, 두 번째 인수가 테이블 자체에 대한 설정이다.
- 시퀄라이즈는 알아서 id를 Primary key로 연결하므로 id 컬럼은 적어줄 필요가 없다. 나머지 컬럼의 스펙을 입력한다. MySQL 테이블과 컬럼 내용이 일치해야 정확하게 대응된다.
▼ 표 7-1 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 |
- super.init 메서드의 두 번째 인수는 테이블 옵션이다.
• sequelize: static init 메서드의 매개변수와 연결되는 옵션으로 db.sequelize 객체를 넣어야 한다. 나중에 model/index.js에서 sequelize를 매개변수로 넘겨줘서 연결한다.
• timestamps: 현재 false로 되어 있으며, 이 속성 값이 true면 시퀄라이즈는 createdAt과 updatedAt 컬럼을 추가한다. 각각 로우가 생성될 때와 수정될 때의 시간이 자동으로 입력된다.
• underscored: 시퀄라이즈는 기본적으로 테이블명과 컬럼명을 캐멀 케이스(camel case)(예시: createdAt)로 만든다. 이를 스네이크 케이스(snake case)(예시: created_at)로 바꾸는 옵션이다.
• modelName: 모델 이름을 설정할 수 있다. 노드 프로젝트에서 사용한다. (보통 모델 이름은 단수형, 테이블 이름은 복수형으로 사용한다.)
• tableName: 실제 데이터베이스의 테이블 이름이다. 기본적으로는 모델 이름을 소문자 및 복수형으로 만든다. 모델 이름이 User라면 테이블 이름은 users가 된다.
• paranoid: true로 설정하면 deletedAt이라는 컬럼이 생긴다. 로우를 삭제할 때 완전히 지워지지 않고 deletedAt에 지운 시각이 기록된다. 로우를 조회하는 명령을 내렸을 때는 deletedAt의 값이 null인 로우(삭제되지 않았다는 뜻)를 조회한다 . 이렇게 하는 이유는 나중에 로우를 복원하기 위해서다. 로우를 복원해야 하는 상황이 생길 것 같다면 미리 true로 설정한다.
• charset과 collate: 각각 utf8과 utf8_general_ci로 설정해야 한글이 입력된다. 이모티콘까지 입력할 수 있게 하고 싶다면 utf8mb4와 utf8mb4_general_ci를 입력한다.
- 이번에는 models/comment.js를 만들어보자.
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' });
}
};
- 여기에는 commenter 컬럼이 없다. users테이블로의 외래키인데 comment모델을 정의할때 넣어도 되지만 다음 절에서 관계를 정의할때 넣어보자.
- 이제 만든 모델들을 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 메서드도 미리 실행해둔다.
7.6.3 관계 정의하기
- user 테이블과 comment 테이블 사이의 관계는 1대다 관계이다.
- 시퀄라이즈에게 이런 테이블 관계를 알려줘 보자.
7.6.3.1 1:N
- 1대N 관계를 시퀄라이즈에서 hasMany, belongsTo 메서드로 표현한다.
- users 테이블의 로우 하나를 불러올 때 연결된 comments의 튜플들을 불러온다.
- comments 테이블의 로우를 불러올 때 연결된 users 테이블의 튜플을 가져온다.
* 왜래키가 속성으로 추가되는 테이블이 belongTo를 호출한다.
// models/user.js
...
static associate(db) {
db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id' });
}
};
// models/comment.js
...
static associate(db) {
db.Comment.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id' });
}
};
- comment 테이블은 user테이블의 id를 commenter(외래키)로하여 자신의 속성으로 갖는다.
- hasMany메서드는 소스키 id를 가지고 comment테이블의 외래키인 commenter를 통해 참조한다.
- foreignKey를 따로 지정하지 않으면 이름이 모델명+기본 키인 컬럼이 모델에 생성된다. 예를 들어 commenter를 foreignKey로 넣어주지 않았다면 user(모델명)+기본 키(id)가 합쳐진 UserId가 foreignKey로 생성된다.(commenter대신 UserId가 속성명이 된다)
- 시퀄라이즈는 테이블이 존재하지 않으면 직접 만들어준다.
7.6.3.2 1:1
- 1:1 관계에서는 hasOne 메서드를 사용한다.
- 사용자 정보를 담고 있는 가상의 Info 모델이 있다고 가정해보자.
db.User.hasOne(db.Info, { foreignKey: 'UserId', sourceKey: 'id' });
db.Info.belongsTo(db.User, { foreignKey: 'UserId', targetKey: 'id' });
- info 테이블에 속성으로 UserId가 추가되기 때문에 belongsTo는 User 테이블에서 사용한다.
7.6.3.3 N:M
- 게시글(Post 테이블)과 해시태그 모델(Hashtag 테이블) 간의 다대다(N:M) 관계를 생각해보자.
- N:M 관계는 belongsToMany 메서드를 사용한다.
db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' });
db.Hashtag.belongsToMany(db.Post, { through: 'PostHashtag' });
- N:M 관계는 특성상 새로운 모델(테이블)이 생성된다
- 새로 생성된 PostHashtag 모델에는 게시글과 해시태그의 아이디가 저장된다.
- 이렇게 만들어진 테이블은 다음처럼 접근 가능하다.
db.sequelize.models.PostHashtag
7.6.4 쿼리 알아보기
- 시퀄라이즈로 자바스크립트를 사용하여 SQL문을 생성해보자.
- 쿼리작업은 프로미스를 반환하므로 async/await 또는 then 과 함께 하용하자.
// user 테이블에 튜플을 하나 생성하는 SQL문과 시퀄라이즈 쿼리비교
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 메서드를 사용한다.
// 로우 조회
SELECT * FROM nodejs.users;
User.findAll({});
// 튜플 하나만 조회
SELECT * FROM nodejs.users LIMIT 1;
User.findOne({});
// 원하는 속성만 가져오기
SELECT name, married FROM nodejs.users;
User.findAll({
attributes: ['name', 'married'],
});
// where절 표현
SELECT name, age FROM nodejs.users WHERE married = 1 AND age > 30;
const { Op } = require('sequelize');
const { User } = require('../models');
User.findAll({
attributes: ['name', 'age'],
where: {
married: 1,
age: { [Op.gt]: 30 },
},
});
- MySQL에서는 undefined라는 자료형을 지원하지 않으므로 where 옵션에는 undefined가 들어가면 안된다.
- 빈 값을 넣고자 하면 null을 대신 사용한다.
- 시퀄라이즈는 자바스크립트 객체를 사용해서 쿼리를 생성해야 하므로 Op.gt 같은 특수한 연산자들이 사용된다.
- Sequelize 객체 내부의 Op 객체를 불러와 사용한다.
- Op.gt(초과), Op.gte(이상), Op.lt(미만), Op.lte(이하), Op.ne(같지 않음), Op.or(또는), Op.in(배열 요소 중 하나), Op.notIn(배열 요소와 모두 다름) 등이 있다.
// OP.or 사용
SELECT id, name FROM users WHERE married = 0 OR age > 30;
const { Op } = require('sequelize');
const { User } = require('../models');
User.findAll({
attributes: ['id', 'name'],
where: {
[Op.or]: [{ married: 0 }, { age: { [Op.gt]: 30 } }],
},
});
// order by 옵션
SELECT id, name FROM users ORDER BY age DESC;
User.findAll({
attributes: ['id', 'name'],
order: [['age', 'DESC']],
});
// 조회할 튜플 개수 제한
SELECT id, name FROM users ORDER BY age DESC LIMIT 1;
User.findAll({
attributes: ['id', 'name'],
order: [['age', 'DESC']],
limit: 1,
});
// offset 속성
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,
});
// 로우 Update 쿼리
UPDATE nodejs.users SET comment = '바꿀 내용' WHERE id = 2;
User.update({
comment: '바꿀 내용',
}, {
where: { id: 2 },
});
// 로우 삭제
DELETE FROM nodejs.users WHERE id = 2;
User.destory({
where: { id: 2 },
});
7.6.4.1 관계 쿼리
- 아래 처럼 튜플 하나를 꺼내서 각 컬럼들에 바로 접근할 수 있다.
const user = await User.findOne({});
console.log(user.nick); // 사용자 닉네임
- 이번에는 join을 사용하는 방법을 알아보자.
- 다음 코드는 사용자가 작성한 댓글들을 가져오는 방법이다.
- include에 참조할 테이블의 이름을 넣어준다. (배열인 이유는 여러개 일 수 있기 때문)
const user = await User.findOne({
include: [{
model: Comment,
}]
});
console.log(user.Comments); // 사용자 댓글
- 아니면 다음처럼 조회할수도 있다.
const user = await User.findOne({});
const comments = await user.getComments();
console.log(comments); // 사용자 댓글
- 관계를 설정했다면 getComments(조회), setComments(수정), addComment(하나 생성), addComments(여러 개 생성), removeComments(삭제) 메서드를 지원한다.
- 동사 뒤의 모델 이름을 바꾸고 싶다면 관계 설정 시 as 옵션을 사용하면 된다.
// 관계를 설정할 때 as로 등록
db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id', as: 'Answers' });
// 쿼리할 때는
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'],
}]
});
// 또는
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);
- 직접 comment 튜플을 인수로 넣어도 되지만 comment.id만 인수로 넣어줘도 동일하다.
- 배열로 여러개 추가.
const user = await User.findOne({});
const comment1 = await Comment.create();
const comment2 = await Comment.create();
await user.addComment([comment1, comment2]);
7.6.4.2 SQL 쿼리하기
- 시퀄라이즈 쿼리를 사용하지 않고 SQL문을 사용할수도 있다.
const [result, metadata] = await sequelize.query('SELECT * from comments');
console.log(result);
7.6.5 쿼리 수행하기
- 모델(테이블)에서 데이터를 받아 페이지를 렌더링하는 방법과 JSON 형식으로 데이터를 가져오는 방법을 알아보자.
- 사용자 정보를 등록하고 사용자가 등록한 댓글을 가져오는 서버를 만들어보자.
- 프로젝트에 view폴더를 만들고 다음과 같이 sequelize.html 파일과 error.html 파일을 만든다.
* https://github.com/zerocho/nodejs-book에서 코드를 복사하자
// views/sequelize.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>시퀄라이즈 서버</title>
<style>
table { border: 1px solid black; border-collapse: collapse; }
table th, table td { border: 1px solid black; }
</style>
</head>
<body>
<div>
<form id="user-form">
<fieldset>
<legend>사용자 등록</legend>
<div><input id="username" type="text" placeholder="이름"></div>
<div><input id="age" type="number" placeholder="나이"></div>
<div><input id="married" type="checkbox"><label for="married">결혼여부</label></div>
<button type="submit">등록</button>
</fieldset>
</form>
</div>
<br>
<table id="user-list">
<thead>
<tr>
<th>아이디</th>
<th>이름</th>
<th>나이</th>
<th>결혼여부</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>{{user.age}}</td>
<td>{{ '기혼' if user.married else '미혼'}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<br>
<div>
<form id="comment-form">
<fieldset>
<legend>댓글 등록</legend>
<div><input id="userid" type="text" placeholder="사용자 아이디"></div>
<div><input id="comment" type="text" placeholder="댓글"></div>
<button type="submit">등록</button>
</fieldset>
</form>
</div>
<br>
<table id="comment-list">
<thead>
<tr>
<th>아이디</th>
<th>작성자</th>
<th>댓글</th>
<th>수정</th>
<th>삭제</th>
</tr>
</thead>
<tbody></tbody>
</table>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="/sequelize.js"></script>
</body>
</html>
// views/error.html
<h1>{{message}}</h1>
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>
- public 폴더 안에 sequelize.js 파일도 만든다.
// public/sequelize.js
// 사용자 이름을 눌렀을 때 댓글 로딩
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 = '';
});
- script 태그에는 버튼들을 눌렀을 때 서버의 라우터로 AJAX 요청을 보내는 코드가 들어 있다.
- 사용할 라우터들을 미리 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'), '번 포트에서 대기 중');
});
- 라우터의 내용은 다음과 같다. sequelize.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을 렌더링할 때 결과값인 users를 넣는다.
- 이렇게 데이터베이스에서 데이터를 조회한 후 템플릿 렌더링에 사용한다.
// routes/users.js
const express = require('express');
const User = require('../models/user');
const Comment = require('../models/comment');
const router = express.Router();
// get,post 요청의 라우트 경로가 같아서
// .route 메서드로 묶어서 표현.
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 메서드에 옵션이 추가되어 있다.
- GET /users/1/comments라면 사용자 id가 1인 댓글을 불러온다.
- 조회된 댓글 객체에는 include로 넣어준 사용자 정보도 들어 있으므로 작성자의 이름이나 나이 등을 조회할 수 있다.
// routes/comments.js
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;
- POST /comments : 댓글을 생성 라우터. commenter 속성에 사용자 아이디를 넣어 사용자와 댓글을 연결한다.
- PATCH /comments/:id : 댓글 수정 라우터
- DELETE /comments/:id : 댓글 삭제 라우터
- 이제 npm start로 서버를 실행하고 http://localhost:3001로 접속하여 동작을 확인해보자.
Executing (default): SELECT `id`, `name`, `age`, `married`, `comment`, `created_at` FROM `users` AS `users`;
// 이하 생략
- Executing으로 시작하는 SQL 구문을 보고 싶지 않다면 config/config.json의 dialect 속성 밑에 "logging": false를 추가한다.
- 접속 시 GET / 라우터에서 User.findAll 메서드를 호출하므로 그에 따른 SQL문이 실행된다.
- 사용자의 이름을 누르면 사용자가 등록한 댓글이 나온다.
- 사용자, 댓글 수정 등록.
- 시퀄라이즈로 모든 데이터베이스 작업을 할 수 없으므로, 나중에는 직접 SQL을 사용해야 하는 경우가 생길 수 있다.
'ComputerScience > NodeJs' 카테고리의 다른 글
node - 9.1 익스프레스로 SNS 서비스 만들기(프로젝트 구조) (0) | 2022.02.05 |
---|---|
node - 8 몽고디비 (0) | 2022.02.03 |
node - 7 MySQL (0) | 2022.01.29 |
node - 6.5 템플릿 엔진 사용하기(nunjucks) (0) | 2022.01.28 |
node - 6.5 템플릿 엔진 사용하기(pug) (0) | 2022.01.28 |