MySQL_시퀼라이즈 사용하기(2)

이애옹·2023년 1월 3일
0

Node.js

목록 보기
27/32

📝 쿼리 알아보기

시퀼라이즈로 CRUD 작업을 하려면, 먼저 시퀼라이즈 쿼리를 알아야 한다. 쿼리는 프로미스를 반환하므로 then을 붙여 결괏값을 받을 수 있다.async/await 문법도 같이 사용할 수 있다.

1) 로우를 생성하는 쿼리

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

다음과 같은 SQL문을 시퀼라이즈 쿼리로 변환하면,

const {User} = require('../models')
User.create({
  name : 'zero',
  age : 24,
  married :false,
  comment : '자기소개'
});

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

한 가지 주의해야 할 점은 데이터를 넣을 때 MySQL의 자료형이 아니라 시퀼라이즈 모델에 정의한 자료형대로 넣어야한다. 이때, 자료형이나 옵션에 부합하지 않은 데이터를 넣었을 경우 시퀼라이즈가 에러를 발생 시킨다.

2) 로우를 조회하는 쿼리

1. 전체 조회

SELECT * FROM nodejs.users;

User.findAll({});

2. 일부 조회

User.find({});

3. 원하는 컬럼만 조회

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

4. 조건에 부합하는 컬럼만 조회
(married가 1이고, age가 30보다 높은 컬럼만 조회)

const {User, Sequelize : { Op } } = require('../models');
User.findAll({
  attributes : ['name','age'],
  where : {
    marride : 1,
    age : {[Op.gt]:30},
  },
});

시퀼라이즈는 자바스크립트 객체를 사용해서 쿼리를 생성하기 때문에 Op.gt 같은 특수한 연산자들이 사용된다.

  • Op.gt : 초과
  • Op.gte : 이상
  • Op.lt : 미만
  • Op.lte : 이하
  • Op.ne : 같지 않음
  • Op.or : 또는
  • Op.in : 배열 요소 중 하나
  • Op.notIn : 배열 요소와 모두 다름

추가로, 쿼리에 order : [['age','DESC']] 구문을 사용하면, 정렬도 가능하다.

또, limit : 1 등의 옵션을 사용하면 조회할 로우 개수를 설정 할 수도 있다.

OFFSET도 역시 offset : 1등으로 구현 가능하다.

2) 로우를 수정하는 쿼리

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

User.update({
	comment : '바꿀 내용',
}, {
	where : { id:2 },
});

2) 로우를 삭제하는 쿼리

DELETE FROM nodejs.users WHERE id = 2;

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

📝 쿼리 수행하기

이제 배운 쿼리를 이용해서 CRUD 작업을 수해하려고 한다. 모델에서 데이터를 받아서 페이지를 렌더링하는 방식과 JSON 형식으로 데이터를 가져오는 방법 총 두가지를 사용 할 예정이다.

💻 쿼리 작성하기

먼저, 다음과 같이 view 폴더에 sequelize.pug 파일을 만들었다. AJAX를 사용해 서버와 통신하도록 만들 예정이다.

  • view/sequelize.pug
doctype html
html
  head
    meta(charset='utf-8')
    title 시퀄라이즈 서버
    style.
      table {
        border: 1px solid black;
        border-collapse: collapse;
      }

      table th, table td {
        border: 1px solid black;
      }
  body
    div
      form#user-form
        fieldset
          legend 사용자 등록
          div
            input#username(type="text" placeholder="이름")
          div
            input#age(type="number" placeholder="나이")
          div
            input#married(type="checkbox")
            label(for="married") 결혼 여부
          button(type="submit") 등록
    br
    table#user-list
      thead
        tr
          th 아이디
          th 이름
          th 나이
          th 결혼여부
      tbody
        for user in users
          tr
            td= user.id
            td= user.name
            td= user.age
            td= user.married ? '기혼' : '미혼'
    br
    div
      form#comment-form
        fieldset
          legend 댓글 등록
          div
            input#userid(type="text" placeholder="사용자 아이디")
          div
            input#comment(type="text" placeholder="댓글")
          button(type="submit") 등록
    br
    table#comment-list
      thead
        tr
          th 아이디
          th 작성자
          th 댓글
          th 수정
          th 삭제
      tbody
    script(src='/sequelize.js')

다음으로, public 폴더 안에 sequelize.js 파일도 만들어준다.

  • public/sequelie.js
// 사용자 이름 눌렀을 때 댓글 로딩
document.querySelectorAll('#user-list tr').forEach(function (el) {
    el.addEventListener('click', function () {
      var id = el.querySelector('td').textContent;
      getComment(id);
    });
  });
  // 사용자 로딩
  function getUser() {
    var xhr = new XMLHttpRequest();
    xhr.onload = function () {
      if (xhr.status === 200) {
        var users = JSON.parse(xhr.responseText);
        console.log(users);
        var tbody = document.querySelector('#user-list tbody');
        tbody.innerHTML = '';
        users.map(function (user) {
          var row = document.createElement('tr');
          row.addEventListener('click', function () {
            getComment(user.id);
          });
          var 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);
        });
      } else {
        console.error(xhr.responseText);
      }
    };
    xhr.open('GET', '/users');
    xhr.send();
  }
  // 댓글 로딩
  function getComment(id) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function () {
      if (xhr.status === 200) {
        var comments = JSON.parse(xhr.responseText);
        var tbody = document.querySelector('#comment-list tbody');
        tbody.innerHTML = '';
        comments.map(function (comment) {
          var row = document.createElement('tr');
          var 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);
          var edit = document.createElement('button');
          edit.textContent = '수정';
          edit.addEventListener('click', function () { // 수정 클릭 시
            var newComment = prompt('바꿀 내용을 입력하세요');
            if (!newComment) {
              return alert('내용을 반드시 입력하셔야 합니다');
            }
            var xhr = new XMLHttpRequest();
            xhr.onload = function () {
              if (xhr.status === 200) {
                console.log(xhr.responseText);
                getComment(id);
              } else {
                console.error(xhr.responseText);
              }
            };
            xhr.open('PATCH', '/comments/' + comment.id);
            xhr.setRequestHeader('Content-Type', 'application/json');
            xhr.send(JSON.stringify({ comment: newComment }));
          });
          var remove = document.createElement('button');
          remove.textContent = '삭제';
          remove.addEventListener('click', function () { // 삭제 클릭 시
            var xhr = new XMLHttpRequest();
            xhr.onload = function () {
              if (xhr.status === 200) {
                console.log(xhr.responseText);
                getComment(id);
              } else {
                console.error(xhr.responseText);
              }
            };
            xhr.open('DELETE', '/comments/' + comment.id);
            xhr.send();
          });
          td = document.createElement('td');
          td.appendChild(edit);
          row.appendChild(td);
          td = document.createElement('td');
          td.appendChild(remove);
          row.appendChild(td);
          tbody.appendChild(row);
        });
      } else {
        console.error(xhr.responseText);
      }
    };
    xhr.open('GET', '/comments/' + id);
    xhr.send();
  }
  // 사용자 등록 시
  document.getElementById('user-form').addEventListener('submit', function (e) {
    e.preventDefault();
    var name = e.target.username.value;
    var age = e.target.age.value;
    var married = e.target.married.checked;
    if (!name) {
      return alert('이름을 입력하세요');
    }
    if (!age) {
      return alert('나이를 입력하세요');
    }
    var xhr = new XMLHttpRequest();
    xhr.onload = function () {
      if (xhr.status === 201) {
        console.log(xhr.responseText);
        getUser();
      } else {
        console.error(xhr.responseText);
      }
    };
    xhr.open('POST', '/users');
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.send(JSON.stringify({ name: name, age: age, married: married }));
    e.target.username.value = '';
    e.target.age.value = '';
    e.target.married.checked = false;
  });
  // 댓글 등록 시
  document.getElementById('comment-form').addEventListener('submit', function (e) {
    e.preventDefault();
    var id = e.target.userid.value;
    var comment = e.target.comment.value;
    if (!id) {
      return alert('아이디를 입력하세요');
    }
    if (!comment) {
      return alert('댓글을 입력하세요');
    }
    var xhr = new XMLHttpRequest();
    xhr.onload = function () {
      if (xhr.status === 201) {
        console.log(xhr.responseText);
        getComment(id);
      } else {
        console.error(xhr.responseText);
      }
    };
    xhr.open('POST', '/comments');
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.send(JSON.stringify({ id: id, comment: comment }));
    e.target.userid.value = '';
    e.target.comment.value = '';
  });

위 두개 파일은 프런트엔드 코드이기 때문에 그냥 복붙해줬다~~

이제 본격적으로 서버 코드를 확인해야한다. script 태그에는 버튼들을 눌렀을 때 서버의 라우터로 AJAX 요청을 보내는 코드가 들어있다.

조금 이따 만들 라우터들을 미리 app.js에 연결 해 준다.

  • app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

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

var app = express();
sequelize.sync();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

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

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

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

이제 라우터의 내용을 추가해준다!

라우터는 GET, POST, PUT, DELETE 요청에 해당하는 라우터를 만들어준다.

  • 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.render('sequelize', { users });
    })
    .catch((err) => {
      console.error(err);
      next(err);
    });
});

module.exports = router;

먼저, GET / 로 접속했을 때의 라우터이다. User.findAll 메서드로 모든 사용자를 찾은 후, sequelize.pug를 렌더링 할 때 결괏값인 users를 넣어준다.

시퀼라이즈는 프로미스를 기본적으로 지원하므로, thencatch를 사용해서 각각 조회 성송 시와 실패 시의 정보를 얻을 수 있다. 이렇게 미리 데이터베이스에서 데이터를 조회한 후 템플릿 렌더링에 사용 할 수 있다.

async/await 문법으로 바꿀 경우
router.get('/',async(req,res,next) => {
try {
const users = await User.findAll();
res.render('sequelize',{ users });
} catch(error) {
console.error(error);
next(error);
}
});

다음은 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,
    age: req.body.age,
    married: req.body.married,
  })
    .then((result) => {
      console.log(result);
      res.status(201).json(result);
    })
    .catch((err) => {
      console.error(err);
      next(err);
    });
});

module.exports = router;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,
    age: req.body.age,
    married: req.body.married,
  })
    .then((result) => {
      console.log(result);
      res.status(201).json(result);
    })
    .catch((err) => {
      console.error(err);
      next(err);
    });
});

module.exports = router;

GET /usersPOST /users 주소로 요청이 들어올 때의 라우터이다. 각각 사용자 조회 요청과 사용자 등록 요청을 처리한다.

GET /에서도 사용자 데이터를 조회했지만, GET / users에서는 데이터를 JSON 형식으로 반환한다는 것에 차이가 있다.

다음은 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;

댓글과 관련된 CRUD 작업을 처리한다. GET /comments, POST /comments, PATCH /comments/:id, DELETE /comments/:id 를 등록한다.

💻 실행결과

브라우저 창에 http://localhost:3000 으로 접속하면 테스트가 가능하다!

1) 메인화면

해당화면이 나온다.

2) 사용자 등록하기

새로운 사용자 정보를 입력 한 후, 등록 버튼을 누르면 사용자 등록이 가능하다.

3) 댓글 등록하기

사용자 아이디(사용자 이름 말고 아이디..)와 댓글 내용을 입력하고 등록 버튼을 누르면 댓글 등록이 된다.

4) 댓글 확인하기

사용자의 이름을 클릭하면 각 사용자들이 작성한 댓글을 확인 할 수 있다.

5) 댓글 수정하기

각 댓글의 수정 버튼을 클릭하면 수정 내용을 입력 하라는 콘솔창이 나온다.

아무 내용도 입력하지 않고 버튼을 누를 경우 경고창이 출력된다.

6) 댓글 삭제하기

각 댓글의 삭제 버튼을 클릭 하면 삭제가 가능하다.

  • 참고로, 해당 내용들은 워크벤치에서도 확인 가능하다.

profile
안녕하세요

0개의 댓글