Sequelize(with MySQL) -2 + 전체 코드 및 설명 ★

백지연·2022년 1월 22일
2

NodeJS

목록 보기
13/26
post-thumbnail

이번 포스팅에서는 4. Sequelize로 CRUD 하기, 5. Sequelize 관계 쿼리, 6. 쿼리 수행 예(전체 코드 설명) 를 다루겠다. 앞선 포스팅과 이어지므로 보고오면 이해에 도움이 될 것이다.

책 Node.js 교과서(개정 2판) 책의 7장의 내용을 참고했다.
+모든 코드는 github주소에 있다.

--

이전내용 정리

  1. MySQL: 테이블 users, comments 생성
  2. Sequelize: 모델 User, Comment 생성
  3. 테이블-모델 연결(User-users, Comment-comments)
  4. Sequelize에게 관계 알림 (users - comments)

4. Sequelize로 CRUD 하기

Sequelize로 CRUD 작업을 하려면 먼저 Sequelize 쿼리를 알아야 한다. 쿼리는 프로미스를 반환하므로 then을 붙여 결과값을 받을 수 있다. async/await 문법도 사용 가능하다.

간단한 예제와 함께 MySQL과 비교하며 Sequelize 쿼리에 익숙해져보자.
나는 이미 MySQL을 공부했어서 MySQL과 비교하며 공부하는 것이 편했다.

1. C: CREATE(생성)

row를 생성하는 쿼리 EXAMPLE )
MySQL

INSTERT INTO nodejs.users (name, age, married, comment) VALUES ('zero', 24, 0, '자기소개1');

Sequelize

const { User } = require('../models'); // 모델명으로 불러옴
User.create({
  name: 'zero',
  age: 24,
  married: false,
  comment: '자기소개1',
});

이 아래부터는 Sequelize 쿼리를 쓸 곳에 const { User } = require('../models');가 있다는 가정 하에 작성했다.

2. R: READ(조회)

사용 메서드

  • findAll({}); : 조건에 해당하는 모든 데이터
  • findOne({}); : 조건에 해당하는 데이터 한 개

테이블의 데이터를 모든 데이터를 조회 EXAMPLE )
MySQL

SELECT * FROM nodejs.users;

Sequelize **findAll 메서드 사용

User.findAll({});

테이블의 데이터를 한 개만 조회 EXAMPLE )
MySQL

SELECT * FROM nodejs.users LIMIT 1;

Sequelize **findOne 메서드 사용

User.findOne({});

테이블의 데이터 중 원하는 컬럼만 조회 EXAMPLE )
MySQL

SELECT name, married FROM nodejs.users;

Sequelize **attributes 옵션 사용

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

테이블의 데이터 중 원하는 컬럼에 조건을 달아 조회 EXAMPLE )
MySQL

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

Sequelize **where 옵션 사용

const { Op } = require('sequelize'); // 시퀄라이즈는 자바스크립트 객체를 사용해 쿼리를 생성해야 하므로 이와 같이 특수한 연산자를 사용함, Sequelize 객체 내부의 Op 객체를 불러와 사용
const { User } = require('../models');
User.findAll({
  attributes: ['name', 'married'],
  where: {
    married: true,
    age: { [Op.gt]: 30 }, // [Op.gt]: 30은 ES2015 문법
  },
});

주의: where 옵션에 undefined가 들어가면 안 되고, 빈 값은 null을 넣어야 한다.
+null과 undefined의 차이를 잘 설명해둔 블로그

[Op.gt]: 30에 대한 추가설명
이게 무슨말인지 처음 만났을때 당황했는데 이 블로그를 보고 깨달았다!!
정리하자면,
1. const { Op } = require('sequelize'); 코드를 보면 구조분해할당으로 sequelize안의 Op 객체를 가져온다.
2. 근데 Op객체는 사실, Operator(연산자)가 들어있는 객체이다!
3. 자주 쓰이는 연산자
Op.gt(초과), Op.gte(이상), Op.lt(미만), Op.lte(이하), Op.ne(같지 않음), Op.or(또는), Op.in(배열 요소 중 하나), Op.notIn(배열 요소와 모두 다름)

Op.or EXAMPLE )
Op.or 속성에 OR 연산을 적용할 쿼리를 배열로 나열하면 된다.
MySQL

SELECT id, name FROM users WHERE married = 0 OR age > 30;

Sequelize

const { Op } = require('sequelize');
const { User } = require('../models');
User.findAll({
  attributes: ['id', 'name'],
  where: {
    [Op.or]: [{ married: false }, {age: {[Op.gt]: 30} }],
  },
});

테이블의 데이터를 정렬되어 보이게 조회 EXAMPLE )
실제로 저장된 데이터베이스가 정렬되지는 않는다. 출력 값만 정렬된다.

MySQL

SELECT id, name FROM users ORDER BY age DESC;

Sequelize **order 옵션 사용(주의: 배열 안에 배열이 있음 - 정렬은 여러 개의 컬럼으로도 가능하기 때문)

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

테이블의 데이터 중 row의 개수를 설정 후 조회 EXAMPLE )
MySQL

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

Sequelize **limit 옵션 사용(몇 개까지 가져올지)

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

테이블의 데이터 중 row 개수를 설정 후 조회 EXAMPLE2 )
MySQL

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

Sequelize **offeset 옵션 사용(몇 번부터 가져올지)

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

3. U: UPDATE(수정)

테이블의 데이터를 수정 EXAMPLE )
MySQL

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

Sequelize **update 메서드 사용

User.update({ // 첫 번째 인수: 수정할 내용
  comment: '바꿀 내용',
}, { // 두 번째 인수: 어떤 로우를 수정할지에 대한 조건 **where 옵션
  where: {id: 2}, 
});

4. D: DELETE(삭제)

테이블의 데이터를 삭제 EXAMPLE )
MySQL

DELETE FROM nodejs.users WHERE id = 2;

Sequelize **destroy 메서드 사용

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

5. Sequelize 관계 쿼리

MySQL로 따지면 JOIN 기능을 지원한다.

메서드 반환 값

  • findAll({}); : 프로미스의 결과로 모델의 배열을 반환
  • findOne({}); : 프로미스의 결과로 모델을 반환

아래의 예시는 1:N(일대다)를 기준으로 작성되었다.
1 (User) : N (Comment)

모델을 반환하는 findOne EXAMPLE )

const user = await User.findOne({});
console.log(user.nick); // 사용자 닉네임

특정 사용자를 가져오면서 그 사람의 댓글까지 가져오는 EXAMPLE )

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

user.Comments에 대해 내가 이해한 내용
나는 분명 User, Comment 모델, users, comments 테이블을 만들 때 Comments를 만든 적이 없다!
내가 이해한 바로는 1:N같이 일대다(hasMany) 관계를 가지면, s가 붙은 이 것(Comments와 같은)을 통해 여러 개의 Comment가 출력이 되는 것이다.
console.log(user)을 통해 이 형태를 찾아주면 사용이 가능하다.

N(Comment: 댓글)에 접근하는 또다른 EXAMPLE )

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

getComments() 추가 설명
관계를 설정했다면 동사 뒤에 모델의 이름이 붙는 형식의 메서드를 지원한다.

  • getComments(조회): 조회
  • setComments(수정): 수정
  • addComment(하나 생성): 하나 생성
  • addComments(여러 개 생성): 여러 개 생성
  • removeComments(삭제): 삭제


    동사 뒤의 모델 이름을 바꾸고 싶다면?
    as 옵션을 쓰자! ** include 시 추가되는 모델(Comment) 객체도 user.Answers로 바뀜
// 관계를 설정할 때 as로 등록
db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id', as: 'Answers'});
// 쿼리할 때는
const user = await User.findOne({});
const comments = await user.getAnswer();
console.log(comments); // 사용자 댓글

관계가 적용된 조회 EXAMPLE ) ** 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']
});

관계가 적용된 생성 EXAMPLE )

const user = await User.findOne({}); // User 모델의 배열의 첫 번째에 있는(첫 번째로 찾은) user 객체를 가져옴
const comment = await Comment.create(); // 빈 comment 만듦

// 1과 2의 결과는 동일
// 1 관계 쿼리 메서드의 인수로 추가할 댓글 모델을 넣음
await user.addComment(comment);

// 2 관계 쿼리 메서드의 인수로 추가할 댓글의 아이디를 넣음
await user.addComment(comment.id);

관계가 적용된 여러개 생성, 배열 EXAMPLE )

const user = await User.findOne({});
const comment1 = await Comment.create();
const comment2 = await Comment.create();
await user.addComment([comment1, comment2]); // 배열 형태로 2개의 comment를 추가함

+Sequelize로 할 수 없는 경우 SQL 쿼리

직접 SQL문을 통해 쿼리할 수도 있다. 그치만 안 쓰는게 좋을 것 같다

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

6. 쿼리 수행 예(전체 코드 설명)

쿼리 수행 예 읽는 법

  1. Git [파일명]이 먼저 제시된다.
  2. 대부분의 설명은 주석으로 담았으며, 추가 설명이 필요한 부분은 블로그에 설명/링크해두었다.
  3. =1>, =2> ... 은 Git [파일명]이 실행되었을 때 이동/실행될 것을 알려준다.
  4. 숫자와 주석을 차근차근 따라가다보면 모든 코드가 이해된다!!
  5. 전체 코드/주석(이)가 궁금하다면? git주소를 확인해보자!

+MySQL 연결을 위한 config.json

Git [package.json]

{
  "name": "delay100_learn-sequelize",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon app"
  },
  "author": "delay100",
  "license": "ISC",
  "devDependencies": {
    "nodemon": "^2.0.15"
  },
  "dependencies": {
    "express": "^4.17.2",
    "morgan": "^1.10.0",
    "mysql2": "^2.3.3",
    "nunjucks": "^3.2.3",
    "sequelize": "^6.13.0",
    "sequelize-cli": "^6.4.1"
  }
}

=1> "main"에 의해 app.js실행

Git [app.js]

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

const { sequelize } = require('./models'); // require('./models/index.js')와 같음 - index.js는 require 시 이름 생략 가능 
const indexRouter = require('./routes');
const usersRouter = require('./routes/users');
const commentsRouter = require('./routes/comments');

const app = express(); // require해온 express 실행 

app.set('port', process.env.PORT || 3001); // 포트번호 3001로 세팅
app.set('view engine', 'html');
nunjucks.configure('views', { // render 시, views 폴더로 이동
    express: app,
    watch: true,
});
sequelize.sync({force: false}) // 서버 실행 시 MySQL과 연동되도록 함, force: true면 서버 실행 시 마다 테이블을 재생성, 테이블을 잘못 만든 경우에 true로 설정
    .then(() => {
        console.log('데이터베이스 연결 성공');
    })
    .catch((err) => {
        console.error(err);
    });

app.use(morgan('dev'));
app.use('/',express.static(path.join(__dirname, 'public'))); // static이라서 사용자가 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'), '번 포트에서 대기 중');
});

=2> http://127.0.0.1:3001/ 로 접속 시, '/'로 선언된 indexRouter에 의해 "routes/index.js" 실행

Git [routes/index.js]

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

const router = express.Router();

// '/' get 요청
router.get('/', async (req,res,next) =>{
    try{
        const users = await User.findAll(); // 모든 유저를 찾아서 sequelize.html에 users로 렌더링
        res.render('sequelize', {users});
    } catch(err){
        console.error(err);
        next(err);
    }
});

module.exports = router;

=3> render에 의해 'views/sequelize.html'로 이동

Git [views/sequelize.html]
**nunjucks의 주석 표시({# #}) 주의해서 볼 것!

<!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>  {# fieldset: 웹 양식의 여러 컨트롤과 레이블(<label>)을 묶을 때 사용 #}
                <legend>사용자 등록</legend>  {# legend: <fieldset> 요소의 캡션(caption)을 정의할 때 사용 #}
                <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> {# thead: table head #}
            <tr> {# <tr>: table row - 가로줄 #}
                <th>아이디</th> {# <th>: table head - 표의 제목 #}
                <th>이름</th>
                <th>나이</th>
                <th>결혼여부</th>
            </tr>
        </thead>
        <tbody> {# tbody: table body #}
            {% 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> {# AJAX(페이지 이동X) - axios 사용 언급 #}
    <script src="/sequelize.js"></script> {# app.js의 app.use('/',express.static(path.join(__dirname, 'public')));에 의해 접근 가능 #}
</body>
</html>

=4> 실행화면1처럼 sequelize.htmlhttp://127.0.0.1:3001/에 표시됨, /sequelize.js에 의해 public/sequlelize.js실행

- 5로 이동: id="user-form"등록을 눌렀을 때
- 6로 이동: id="comment-form"등록을 눌렀을 때

Git [public/sequelize.js] 中 사용자 등록(user-form)

// 사용자 등록 시
document.getElementById('user-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const name = e.target.username.value; // e.target:user-form이라는 id를 가진 target, username: id 이름이 username, value: 들어있는 값
    const age = e.target.age.value;
    const married = e.target.married.checked;
    console.log(name);
    if(!name){
        return alert('이름을 입력하세요');
    }
    if(!age){
        return alert('나이를 입력하세요');
    }
    try{
        await axios.post('/users', {name, age, married}); // app.js의 '/users'에 의해 routes/users.js의 post가 실행(post에 name, age, married 값이 전송됨)
        getUser(); // getUser() 함수 실행
    } catch(err){
        console.error(err);
    }
    // 입력한 것 초기화
    e.target.username.value = '';
    e.target.age.value = '';
    e.target.married.checked = false;
});

=5> 5-1, 5-2가 순차적으로 실행된 후 User가 등록/표시

5-1: axios.post에 의해 routes/users.jspost가 실행, post에 name, age, married 값이 전송됨

5-2: 5-1에 의해 DB에만 새로운 User가 생성된 상태에서 getUser() 실행

Git [routes/users.js] 中 '/'의 post

.post(async (req, res, next) => {
        try {
            const user = await User.create({ // 유저모델 생성
                name: req.body.name, // sequelize.js에서 전송받은 name값을 가져옴
                age: req.body.age, // sequelize.js에서 전송받은 age값을 가져옴
                married: req.body.married, // sequelize.js에서 전송받은 married값을 가져옴
            });
            console.log(user);
            res.status(201).json(user); // 성공 user(응답) 전송
        } catch(err){
            console.error(err);
            next(err);
        }   
    });

+참고) Git [models/user.js]

const Sequelize = require('sequelize');

module.exports = class User extends Sequelize.Model { // User 모델을 만들고 모듈로 exports함(User 모델은 Sequelize.Model을 확장한 클래스)
    static init(sequelize){ // 테이블에 대한 설정 <-> static associate: 다른 모델과의 관계
        return super.init({ // super.init의 첫 번째 인수: 테이블에 대한 컬럼 설정
            name: {
                type: Sequelize.STRING(20), // STRING: MySQL의 VARCHAR 
                allowNull: false, // allowNull: MySQL의 NOT NULL
                unique: true, // unique: MySQL의 UNIQUE
            },
            age: {
                type: Sequelize.INTEGER.UNSIGNED, // INTEGER: MySQL의 INT
                allowNull: false,
            },
            married: {
                type: Sequelize.BOOLEAN, // BOOLEAN: MySQL의 TINYINT
                allowNull: true,
            },
            comment: {
                type: Sequelize.TEXT,
                allowNull: true,
            },
            create_at: {
                type: Sequelize.DATE, // DATE: MySQL의 DATETIME
                allowNull: false,
                defaultValue: Sequelize.NOW, // defaultValue: MySQL의 default, Sequelize.NOW: MySQL의 now()
            },
        }, {  // super.init의 두 번째 인수: 테이블 자체에 대한 설정(테이블 옵션)
            sequelize, // static init 메서드의 매개변수와 연결되는 옵션
            timestamps: false, // true: Sequelize가 자동으로 createdAt과 updatedAt 컬럼을 추가
            underscored: false, // true: create_at같이(스네이크 케이스), false: createdAt같이(캐멀 케이스) 
            modelName: 'User', // 모델 이름
            tableName: 'users', // 테이블 이름
            paranoid: false, // 컬럼을 지워도 완전히 지워지지 않고 deletedAt이라는 컬럼이 생김(지운 시각이 기록됨)
            charset: 'utf8', // 한글 입력, 이모티콘까지 입력: utf8mb4
            collate: 'utf8_general_ci', // 한글 입력, 이모티콘까지 입력: utf8mb4_general_ci
        });
    }
    static associate(db){ // 다른 모델과의 관계 <-> static init: 테이블에 대한 설정
        db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id'});
    }
}

=5-1> DB에만 새로운 User가 생성됨

Git [public/sequelize.js] 中 getUser()

  • DOM: 브라우저 안에 출력될 HTML 창에 대한 객체 / DOM 트리의 루트는 document 객체(출처: document)
  • innerHTML
// 사용자 로딩 함수
async function getUser(){   
    try{
        const res = await axios.get('/users');  // app.js의 '/users'에 의해 routes/users.js의 get이 실행되고 결국, User.findAll()이 가져와짐
        const users = res.data; // .data는 res의 데이터가 존재함
        // console.log(users);
        const tbody = document.querySelector('#user-list tbody'); // document: html의 가장 상위 객체(설명 블로그 참조) 
        tbody.innerHTML = ''; // 일단 tbody에 대한 모든 내용을 초기화함
        users.map(function (user){ // map: 반복문
            const row = document.createElement('tr'); // tr을 생성하면서 빈 row 객체 생성
            // 추가된 데이터에 대한 listener도 필요
            row.addEventListener('click', () => { // 이 row를 클릭하면 실행
                getComment(user.id); // getComment 함수 실행(파라미터로 user의 id를 줌)
                console.log(user.id);
            });
            // row 셀 추가
            let td = document.createElement('td'); // 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);
    }
}

=5-2> 실행화면2처럼 DB에 있는 User 데이터가 화면에 보이게 됨, 중간에 getComment(user.id)는 =6-2> 참고

Git [public/sequelize.js] 中 댓글 등록(comment-form)
** 코드의 내용은 5와 유사(참고하면 됨)

// 댓글 등록 시
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); // getComment 함수 실행, 파라미터로 userid를 보냄
    } catch(err){
        console.error(err);
    }
    e.target.userid.value='';
    e.target.comment.value='';
});

=6> 6-1, 6-2가 순차적으로 실행된 후 Comment가 등록/표시

6-1: axios.post에 의해 routes/comments.jspost가 실행, post에 id, comment 값이 전송됨

6-2: 6-1에 의해 DB에만 새로운 Comment가 생성된 상태에서 getComment() 실행

Git [routes/comments.js] 中 '/'의 post

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

+참고) Git [models/comment.js]
**foreigKey에 commenter을 확인할 수 있음

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

=6-1> DB에만 새로운 comments가 생성됨

Git [public/sequelize.js] 中 getComment(id)

// 댓글 로딩 함수
async function getComment(id) {
    try{
        const res = await axios.get(`/users/${id}/comments`);  // app.js의 '/users'에 의해 routes/users.js의 '/:id/comments'가 실행
        const comments = res.data;
        const tbody = document.querySelector('#comment-list tbody');
        tbody.innerHTML = '';
        comments.map(function (comment) { // map: 반복문
            // row 셀 추가
            const row = document.createElement('tr');
            let td = document.createElement('td'); // 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 = '수정';
            console.log(edit);
            edit.addEventListener('click', async () => { // 수정 클릭 시 
                const newComment = prompt('바꿀 내용을 입력하세요');
                if (!newComment){
                    return alert('내용을 반드시 입력하셔야 합니다');
                }
                try {
                    await axios.patch(`/comments/${comment.id}`, { comment: newComment });  // app.js의 '/users'에 의해 routes/comments.js의 '/:id'의 patch가 실행, 새로 작성한 수정 글도 보냄
                    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}`); // app.js의 '/users'에 의해 routes/comments.js의 '/:id'의 delete가 실행
                    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);
    }
}

=6-2> 실행화면3처럼 파라미터로 온 user.id에 맞는 댓글만 출력되고, 실행화면4처럼 댓글 수정, 실행화면5처럼 댓글 삭제

+참고)

Git [public/sequelize.js] 中 처음 html 로딩 시 실행됨

// html이 로딩되었을 때 실행됨(기존 데이터)
// 사용자 이름을 눌렀을 때 댓글 로딩
document.querySelectorAll('#user-list tr').forEach((el) => {  // id: #, el: element 반복문
    el.addEventListener('click', function() {
        const id = el.querySelector('td').textContent; // body 값(모든 요소를 포함한)을 하나씩 불러옴 (user.id, user.name, user.age, '기혼' if user.married else '미혼')
        getComment(id); // getComment 함수 호출(아래에 작성되어있음)
    });
});

Git [models/index.js]
**model과 DB를 관리

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]; // config/config.json에서 데이터베이스 설정을 불러옴
const db = {};

const sequelize = new Sequelize(config.database, config.username, config.password, config); // new Sequelize를 통해 MySQL 연결 객체 생성

db.sequelize = sequelize; // 연결 객체를 나중에 재사용 하기 위해 db.Sequelize에 넣음
db.Sequelize = Sequelize;

// db 객체에 User, Comment 모델을 담음 -> 앞으로 db를 require해서 User, Comment에 접근 가능
db.User = User;
db.Comment = Comment;

// 각 모델의 static init을 호출, init이 실행되어야 테이블이 모델로 연결(테이블-모델 연결)
User.init(sequelize);
Comment.init(sequelize);

// 다른 테이블과 관계를 연결
User.associate(db);
Comment.associate(db);

module.exports = db; 

--

실행 화면

실행화면(console)

실행화면 1(웹 브라우저) - http://127.0.0.1:3001

실행화면 2(웹 브라우저) - 사용자 등록

실행화면 3(웹 브라우저) - 댓글 등록 ** 사용자 등록의 아이디 클릭 시 작성자별 작성한 댓글이 보이게 됨!

실행화면 4(웹 브라우저)

  • 댓글 수정 화면

  • 댓글 수정 결과

실행화면 5(웹 브라우저) - 댓글 삭제

디렉토리 구조


오타가 너무 많아서 실행하기까지 힘들었다,... 특히 코드들이 왔다갔다 하면서 실행해서 머리가 복잡해졌었다...그래도 2일동안 이 코드들만 본 결과!! 나름 잘 이해한 것 같아 뿌듯하다😊

잘못된 정보 수정 및 피드백 환영합니다!!

profile
TISTORY로 이사중! https://delay100.tistory.com

2개의 댓글

comment-user-thumbnail
2022년 1월 23일

진도가 빠르시네요~^^

1개의 답글