[4일차] Node.js, MongoDB - Part 2 : 게시물마다 고유번호를 달아 저장하기, DB Update 함수와 inc 연산자, AJAX로 삭제요청하기 (DB delete 함수, jQuery를 이용한 UI 기능 & 여러가지 응답방법)

흑염소·2023년 8월 31일

📕 게시물마다 고유번호를 달아 저장하기

_id번호를 다는 이유와 중요성

MongoDB에 데이터를 저장할 땐 _id라는 값을 꼭 넣어야한다.
안넣을 경우 MongoDB에서 알아서 objectId() 를 만들어서 강제로 부여해주지만
관리를 위해서 숫자타입으로 직접 지정해주는게 좋다.
이전에 작성했던 post요청 처리하는 서버코드를 다시 살펴보자.

app.post('/add', function(요청, 응답){
  응답.send('전송완료');
  
  db.collection('post').insertOne( { _id : 지금까지 발행한 게시물 갯수 + 1, 제목 : 요청.body.title, 날짜 : 요청.body.date } , function(){
    console.log('저장완료')
  });
  
});

insertOne() 함수 안에 id 항목을 작성해주면 되는데 당연하게 하드코딩은 안된다.
게시물마다 각 고유의 1, 2, 3 ... 숫자를 달아주고 싶은데 발행될 때 마다 자동으로 +1 하는 기능을 구현해야 한다.
다른 DB에선 자동으로 증가시켜주는 Auto Increment 기능이 있는데 MongoDB는 없음
그래서 내가 지금까지 몇번 게시물을 발행했는지 어딘가에 기록해줘야 한다.

mongodb atlas 홈페이지에 접속해서 대시보드에서 collection 하나를 더 만든다.

좌측에 작업중인 todoapp 데이터베이스에 추가로 collection을 만들고 counter라고 작명한다.
이제 여기에 지금까지 발행한 게시물 갯수를 저장하도록 하자.

INSERT DOCUMENT로 항목을 추가하고 다음과 같이 설정해준다.
그대로 작성 후 INSERT 해주면 되는데 숫자형 문자형 설정도 제대로 해주도록 하자.
이제 totalPost 라는 곳에 고유 id 값이 저장된다.
POST 요청하는 서버코드를 다시 수정하자.

app.post('/add', function(요청, 응답){
  
  db.collection('counter').findOne({name : '게시물갯수'}, function(에러, 결과){
    var 총게시물갯수 = 결과.totalPost;
    
    db.collection('post').insertOne( { _id : (총게시물갯수 + 1), 제목 : 요청.body.title, 날짜 : 요청.body.date } , function(){
      console.log('저장완료');
      응답.send('전송완료');
    });
  });
  
});

findOne() 함수는 collection 내에서 내가 원하는 속성을 가지고 있는 문서를 찾아주는 함수다.
그럼 찾은 결과가 콜백함수 function내의 결과라는 이름의 변수에 담긴다.
변수를 이용해서 totalPost 속성을 가져올 수 있게 됐다.
totalPost를 총게시물갯수라는 변수에 담고 post라는 collection에 inserOne() 시, 총게시물갯수 +1한 숫자를 id값으로 함께 지정한다.
그리고 응답.send 라는 코드를 이용해 응답해주고 마무리한다. (응답.어쩌구는 꼭 들어있어야함)

코드는 한눈에 보고 이해하려 하지 말고 한줄한줄 읽어나가는게 정석이다.
이해 안될경우 한줄씩 다시 보면서 생각하자.

((요약))
1번줄 : 누군가 /add 경로로 post 요청을 하면
2번줄 : counter라는 콜렉션에서 총게시물갯수 저장해놓은 문서를 찾는다. 그 찾은 문서는 결과라는 변수에 담겨옴.
3번줄 : 결과.totalPost하면 총게시물 갯수가 뿅하고 출현. 그걸 var 총게시물갯수 변수에 저장해서 사용.
4번줄 : 이제 글저장 하면 됨. post라는 콜렉션에 insertOne을 이용해 게시물을 추가. 추가할 때 _id를 var 총게시물갯수를 이용해 제대로 부여.
6번줄 : 성공했다고 응답.send로 브라우저에게 글자를 보냄. 응답.render, 응답.redirect 이런 것도 이용가능함.

뭔가 하나의 기능이 빠져있다.
글이 추가되면 counter 콜렉션 내의 갯수도 증가해야한다.
즉, DB 데이터를 수정해야 하는데 이부분을 아래 파트에서 알아보자.

📗 DB Update 함수와 $inc 연산자

DB update 함수 updateOne()

updateOne 함수엔 파라미터 3개가 필요하다.

db.collection('counter').updateOne( {이런 이름의 자료를} , {이렇게 수정해주세요} , function(에러, 결과){
  console.log('수정완료')
})
  1. 왼쪽 - {name : '게시물갯수'} 이렇게 자료로 찾을 수 있는 이름이나 쿼리문 입력

  2. 중앙 - 내가 수정할 값을 입력
    { $set: {totalPost : 100} } 값을 아예 100으로 변경하기
    { $inc: {totalPost : 5} } 값을 5만큼 더해주기

  3. 오른쪽 - 그냥 콜백함수. 수정 실패나 성공시 실행됨

counter 콜렉션의 totalPost 속성을 아래처럼 업데이트 시키면 된다.
업데이트 콜백함수로 응답.send도 해주자.

app.post('/add', function (요청, 응답) {
  db.collection('counter').findOne({name : '게시물갯수'}, function(에러, 결과){
    var 총게시물갯수 = 결과.totalPost

    db.collection('post').insertOne({ _id : 총게시물갯수 + 1, 제목 : 요청.body.title, 날짜 : 요청.body.date }, function (에러, 결과) {
      
      db.collection('counter').updateOne({name:'게시물갯수'},{ $inc: {totalPost:1} },function(에러, 결과){
	if(에러){return console.log(에러)}
        응답.send('전송완료');
      })
    })

  })
})

📘 AJAX로 삭제요청하기

요청에는 4가지 종류, GET POST PUT DELETE 가 있다.
근데 HTML form에서 일반적으로 PUT DELETE는 요청할 수 없음.
HTML 언어 만들 때 GET, POST만 가능하게 해놨다.
그래서 삭제요청을 할 때 쓰는 3가지 방법이 있다.

  1. method-override 라이브러리 도움을 받는다.
  2. AJAX로 DELETE 요청을 한다.
  3. 그냥 POST 요청을 날리고 DELETE 작업을 수행한다.

여기서 2번 방법을 배워보도록 하자.
사실 3번이 가장 편리하지만(POST로 삭제하라고 요청해도 상관없음) REST한 API를 만들기 위해 1번과 2번을 쓰는 것 뿐이다.

AJAX

프론트엔드에서 Javascript를 이용해 서버에 여러가지 요청을 할 수 있는 문법이다.
새로고침 없이도 서버에 몰래몰래 요청을 할 수 있는게 장점이다.
스무스한 사이트를 만들고 싶으면 AJAX 문법을 이용해 처리하면 된다.

설치

쌩 자바스크립트로 AJAX가 가능하지만 코드가 길어서 jQuery를 사용해보자.
별도의 설치는 필요없다.
list.ejs 파일에 Bootstrap이 정상적으로 설치되었다면 같이 딸려 들어와있다.
약간의 수정만 거치면 된다.

// 기존 코드
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="어쩌구" crossorigin="어쩌구" ></script>

// 이렇게 수정해준다
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>

slim 버전은 AJAX 요청이 불가능하기 때문에 정식버전으로 수정해주면 된다.
이제 jQuery 불러온 코드 하단에 script를 짜주면 된다.

주의) AJAX는 jQuery 설치 파일보다 하단에 script 태그 열어서 작성해야 잘 작동됨

AJAX 기본문법(jQuery 사용)

(list.ejs 하단)

<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>

<script>
  $.ajax({
    method : 'DELETE',
    url : '/delete',
    data : '밥먹기'
  })
</script>

풀이해보자면, /delete 경로로 DELETE 요청을 하는데 요청과 함께 '밥먹기'라는 데이터를 보내달라는 의미다.
코드는 script 태그 안에 그대로 넣으면 list.ejs 페이지를 방문할 때 마다 바로 실행된다.
하지만 새로고침 현상이 없으니 요청이 성공했는지 실패했는지 알 수가 없다.
완성형 문법으로 고쳐보자.

(list.ejs 하단)

<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>

<script>
  $.ajax({
    method : 'DELETE',
    url : '/delete',
    data : {_id : 1}
  }).done(function(결과){
    AJAX 성공시 실행할 코드는 여기
  }).fail(function(에러){
    실패시 실행할 코드는 여기
  });
</script>

ajax 요청 뒤에 .done.fail 함수를 체인할 수 있는데(jQuery 썼으니까)
요청이 성공할 경우 .done 함수에 콜백함수, 실패할 경우 .fail함수에 콜백함수로 어떤걸 실행해라 써주면 된다.

요청하는 코드는 완성됐으니 서버 코드를 작성해보자.

DB delete 함수 deleteOne()

(server.js)

app.delete('/delete', function(요청, 응답){
  db.collection('post').deleteOne(요청.body, function(에러, 결과){
    console.log('삭제완료')
  })
  응답.send('삭제완료')
});

app.delete()로 요청을 명시하고 원하는 데이터베이스의 삭제할 내용을 작성해준다.
/delete 경로로 보낸 요청들이 담겨있는데 deleteOne() 함수를 쓰면 원하는 데이터를 삭제 가능하다.
deleteOne(삭제원하는 데이터이름, function(){}) 이렇게 쓰면 된다.
그리고 AJAX 요청시 data : {_id : 1}이라고 적은 정보는 요청.body에 담겨온다.
그래서 그 정보를 그대로 deleteOne()에 집어넣으면 해당 데이터 삭제 가능함.

근데 삭제가 안된다.
이유는 타입때문에 그럼
출력해보면 DB에 저장된 데이터는 {_id : 1} 숫자형이고
AJAX로 요청 받아온 데이터는 {_id : '1'} 문자형이다.
타입이 안맞아서 서버에서 일치하는 데이터를 찾을 수 없는 것이다.
타입을 수정해보자.

(server.js)

app.delete('/delete', function(요청, 응답){
  요청.body._id = parseInt(요청.body._id)
  db.collection('post').deleteOne(요청.body, function(에러, 결과){
    console.log('삭제완료')
  })
  응답.send('삭제완료')
});

요청.body 중, 요청.body._id의 값을 parseInt를 통해 정수로 변환하고 재할당 해준다.
이러면 새로고침해서 list.ejs 페이지 불러오면 제대로 삭제된다.

이제 새로고침 말고 버튼을 눌렀을 때 작동하게 해보자.
지금은 script 태그 안에 담은 코드들이라서 페이지 로드할 때마다 AJAX 요청되니까 고쳐야함

버튼 이벤트 걸기

(list.ejs)

<ul class="list-group">
  <% for (var i = 0; i < posts.length; i++){ %>
  <li class="list-group-item">
    <h4> 할일 제목 : <%= posts[i].제목 %> </h4>
    <p> 할일 마감날짜 : <%= posts[i].날짜 %> </p>
    <button class="delete">삭제</button>
  </li>
  <% } %>
</ul>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>

<script>
  $('.delete').click(function(){
    $.ajax({
      method : 'DELETE',
      url : '/delete',
      data : { _id : 1 }
    }).done(function(결과){
      //AJAX 성공시 실행할 코드는 여기
    })

  });
</script>

button 태그에 class를 추가했고 click이벤트를 걸어줬다.
jQuery 문법 이용해서 작성했고 이제 버튼 클릭시마다 잘 작동함.

jQuery를 이용한 UI 기능 & 여러가지 응답방법

마지막 단계로, 하드코딩된 { _id : 1 } 부분을 클릭이벤트 발생한 대상의 글번호가 들어오게 바꾸면 된다.

<ul class="list-group">
  <% for (var i = 0; i < posts.length; i++){ %>
  <li class="list-group-item">
    <h4> 할일 제목 : <%= posts[i].제목 %> </h4>
    <p> 할일 마감날짜 : <%= posts[i].날짜 %> </p>
    <button class="delete" data-id="<%= posts[i]._id %>">삭제</button>
  </li>
  <% } %>
</ul>

<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>

<script>
  $('.delete').click(function(){
    $.ajax({
      method : 'DELETE',
      url : '/delete',
      data : { _id : e.target.dataset.id }
    }).done(function(결과){
      //AJAX 성공시 실행할 코드는 여기
    })
  });
</script>

button 태그에 data-id라는 속성을 추가하고 data: {} 부분을 변경했다.

  1. HTML요소에는 data-마음대로작명="원하는값" 이런 식으로 내가 만든 속성을 넣을 수 있다.
    이걸 이용해서 게시글마다의 고유 _id값을 버튼에 넣어준다.

  2. 자바스크립트로 버튼에 지정된 data-속성명="값" 정보를 가져올 수 있다.
    요소.dataset.속성명 이렇게 가져오면 됨

  3. e.target으로 이벤트가 발생한 대상을 알아낼 수 있다.
    e.target.dataset.id는 내가 클릭한 버튼의 data-id를 읽어오게 된다.
    즉, 2번글 버튼을 누르면 data : { _id : 2 }
    3번글 버튼을 누르면 data : { _id : 3 } 가 채워지게 됨

이제 버튼을 클릭했을 때, 해당 게시글번호를 가진 데이터만 삭제된다.
하지만 아직도 삭제버튼 누르면 HTML 화면은 변동이 없고 DB데이터만 지우고 있다. (AJAX는 새로고침 없이 작동하니까 당연)
그렇다면 유저들의 편의성이 떨어지니 부드러운 서비스를 위해 삭제할 게시글 li태그를 지워주도록 하자.

(list.ejs)

<script>
  $('.delete').click(function(){
    $.ajax({
      method : 'DELETE',
      url : '/delete',
      data : { _id : e.target.dataset.id }
    }).done((결과)=>{
      // li태그 지우는 코드 한줄 추가
      $(this).parent('li').fadeOut();
    })
  });
</script>

이벤트가 발생한 대상의 parent 중 li 요소를 선택해서 fadeOut 시켰다.
잘 작동한다.
끝!

profile
매일 TIL 중인 비전공자 프론트 개발자

0개의 댓글