Node.js

node> 수정기능 만들기 (method-override, MongoDB 수정문법 추가)

연습노트 2024. 7. 25. 09:30

글 수정기능을 만들어볼건데 

실은 수정기능 만드는건 상세페이지 했던거랑 약간 비슷한 것 같은데 

실력향상을 원하시면 강의끄고 직접 글 수정기능 만들어봅시다. 

혼자 코드를 짜봐야 혼자 코드짜는 실력이 늡니다.  

겁쟁이들은 저랑같이 해보고요 

 

 

 

 

 

수정기능이 뭐냐면

 

수정기능이 뭔지 한글로 설명부터 해봅시다.

다들 게시판서비스에서 글 수정같은거 해봤을거 아닙니까

 

1. 글마다 있는 수정버튼 누르면 글수정할 수 있는 페이지로 이동

2. 그 페이지엔 글의 제목과 내용이 이미 폼에 채워져있어야함

3. 전송누르면 그걸로 기존에 있던 document를 수정해줌 

여기서 모르는건 기존 document 수정하는 법 정도인 것 같은데 1번부터 해보도록 합시다. 

 

 

 

 

 

1. 글마다 있는 수정버튼 누르면 글 수정페이지 이동

 

버튼누르면 글 수정페이지로 이동시키고 싶으면 

<a href="/어쩌구">링크</a> 만들면 되겠죠? 

그리고 수정페이지의 URL도 하나 정하면 될거같습니다.

/edit 이걸로 합시다.

거기로 이동하면 edit.ejs 파일같은거 만들어서 보내주면 될 것 같습니다. 

 

<a href="/edit">✏️</a>

그래서 list.ejs 적절한 위치에 링크 하나 추가해봤습니다. 

글제목 옆이 좋지 않을까요 

 

근데 잘 생각해보시면 지금 글이 4개 있는데 수정페이지가 몇 개 필요하죠?

4개 필요합니다.

첫글 수정페이지 들어가면 첫글의 제목과 내용이 박혀있어야 하고

둘째글 수정페이지 들어가면 둘째글의 제목과 내용이 박혀있어야 합니다. 

 

 

app.get('/edit/1', (요청, 응답) => {
  응답.render('첫째글 수정페이지')
})

app.get('/edit/2', (요청, 응답) => {
  응답.render('둘째글 수정페이지')
})

... 계속

그래서 서버파일에다가 이렇게 4개의 수정페이지를 만들어두면 됩니다. 

근데 글이 1만개 있으면 어쩌죠?

1만번 복붙하면 되긴 하는데 

이런 뻘짓거리 싫으면 URL 파라미터 문법 사용하면 된다고 한 것 같습니다. 

 

 

app.get('/edit/:id', (요청, 응답) => {
  응답.render('edit.ejs')
})

이러면 됩니다. 

기념으로 수정페이지도 edit.ejs 이름으로 하나 만들어서 보내줍시다.  

edit.ejs 페이지 레이아웃은 그냥 write.ejs 그대로 복붙해서 쓰도록 합시다. 

(다른 게시판들 보면 수정누르면 글작성화면이랑 똑같은거 뜨지 않습니까)

 

그럼 이제 수정페이지 이동하는 링크도 만들 수 있을텐데 그건 나중에 해보도록 합시다. 

 

 

 

 

 

2. 수정페이지엔 글의 제목과 내용이 이미 채워져있어야함

 

수정페이지 들어가면 기존 글의 내용이 박혀있어야합니다.  

그럼 edit.ejs 보내줄 때 글 내용을 DB에서 꺼내서 ejs파일에 박아주면 되겠군요.

 

 

app.get('/edit/:id', async (요청, 응답) => {
  let result = await db.collection('post').findOne({ _id : new ObjectId(첫글_id) })
  응답.render('edit.ejs', {result : result})
})

document 하나 찾고 싶으면 .findOne 쓰면 됩니다. 

그래서 대충 이렇게 작성하면 첫 글이 채워진 수정페이지가 완성된 것 같은데

근데 이러면 안되겠죠? 

지금 어떻게 접속하든간에 매번 똑같은 첫 글의 제목과 내용만 보여줄 것 아닙니까. 

어쩔땐 둘째글, 어쩔땐 셋째글 내용을 보여주고 싶군요. 

 

 

 

저번에 이럴 때 어떻게 했는지 기억을 되살리면 

유저가 /edit/뒤에 글의 id를 입력해서 요청하면 

그 id를 가진 글의 제목과 내용을 DB에서 뽑아서 ejs파일로 보내주면 해결인듯요. 

그래서 잘 생각해보면 수정페이지도 상세페이지랑 뭔가 비슷합니다. 

 

 

 

app.get('/edit/:id', async (요청, 응답) => {
  let result = await db.collection('post').findOne({ _id : new ObjectId(유저가URL파라미터자리에입력한거) })
  응답.render('edit.ejs', {result : result})
})

그래서 코드 이렇게 바꾸면 됩니다.

유저가URL파라미터자리에입력한거가 코드로 뭐냐고요? 

까먹은건 검색해보면 됩니다. 

 

 

 

그래서 진짜있는 글의 _id를 넣어서 /edit/글_id 로 접속해보면 

edit.ejs 파일이 잘 나오는데 

 

 

▲ 근데 <input>에 글들이 채워져있진 않군요. 

<input>에 글을 미리 입력해두고 싶으면 value="미리입력할값" 속성을 추가하면 됩니다. 

 

(edit.ejs)

(생략)
<input name="title" value="<%= result.title %>">
<input name="title" value="<%= result.content %>">

그래서 result.title 같은걸 채워봤습니다.

변수는 언제나 어떤 값이 들어있을지 모르기 때문에 사용 전에 출력해보는게 좋은 습관입니다. 

 

 

 

 

 

 

 

3. 전송누르면 그걸로 기존에 있던 document를 수정해줌 

 

이제 3번기능 만들 것인데 

전송버튼 누르면 서버로 글을 전송할텐데 

그럼 서버는 그걸 검사하거나 하고 DB에 있던 내용을 그걸로 수정해주면 됩니다. 

DB에 있는 document 수정하는 법만 알려드릴테니까 집가서 3번기능 구현해오십시오. 

 

db.collection('post').updateOne( {수정할document정보}, {$set: {덮어쓸내용}}) 

DB에 있던 document 하나를 수정하고 싶으면 이거 쓰면 됩니다. 

 

 

db.collection('post').updateOne( { a : 1 }, {$set: { a : 2 }}) 

예를 들어 이렇게 작성하면 a : 1을 가진 document를 찾아서 a 항목을 2로 수정해줍니다. 

 

 

db.collection('post').updateOne( { _id : 2 }, {$set: { a : 3 }}) 

예를 들어 이렇게 작성하면 _id : 2를 가진 document를 찾아서 a 항목을 3으로 수정해줍니다.

 

일단 수정페이지 이동 링크부터

 

저는 수정페이지로 쉽게 이동하고 싶으니까 링크부터 만들어볼 것입니다. 

/list 페이지로 가봅시다. 

 

(list.ejs)

<div class="white-bg">
  <% for (let i = 0; i < 글목록.length; i++){ %>
    <div class="list-box">
      <h4>
        <a href="/detail/<%= 글목록[i]._id %>">
          <%= 글목록[i].title %>
        </a>
        <a href="/edit/글의_id">✏️</a>
      </h4>
      <p>글내용임</p>
    </div>
  <% } %>
</div>

연필 누르면 /edit/글의_id 로 이동하게 코드짜봅시다. 

글의_id 는 코드로 뭐냐고요? 

위에 <%= 글목록 %> 변수를 쓰면 모든 글이 담겨있는데 거기에 _id도 있을 것 같군요.

그래서 글의_id 자리엔 <%= 글목록[i]._id %> 집어넣으면 될듯요 

 

하지만 언제나 지레짐작하면서 코딩하면 안되고 어떤 값이 들어있는지 출력해보는게 좋은 습관입니다. 

코딩을 책이나 글로 따라치기만 했던 분들이 보통 지레짐작코딩하는 경우가 많던 것 같습니다. 

 

 

 

 

 

 

3. 전송버튼 누르면 그걸로 기존에 있던 document를 수정

 

이거 구현하면 끝인데 

유저가 직접 DB에 있는걸 수정하게 냅둘 순 없기에 

서버로 글을 우선 전달부터하고 서버는 그걸 확인한 다음에 DB수정을 해줍시다.

서버로 전달하는건 

 

 

(edit.ejs)

<form class="form-box" action="/edit" method="POST">
  (생략)
</form>

edit.ejs 파일 가보시면 <form>이 있는데

여기다가 method, url 잘 기입하면 서버로 글을 전송할 수 있습니다. 

저는 /edit, POST를 선택해봤습니다. 

 

(참고) <form> 에선 GET, POST만 가능하고 PUT, DELETE 요청은 보낼 수 없습니다. 

PUT 같은거 쓰면 좀 더 이쁜 API를 만들 수 있다고 했는데 맘대로 쓸 수 없는 냉혹한 세상임 

다음 강의에 나올 method-override 쓰면 될 수도 있습니다. 

 

 

 

 

 

app.post('/edit', async (요청, 응답)=>{
  await db.collection('post').updateOne({ ??? },
    {$set : { ??? }
  })
  응답.redirect('/list')
}) 

그럼 서버에서도 /edit으로 POST요청받는 API 하나 만들어봅시다. 

누가 /edit으로 post요청하면 db게시물 수정해주세요 라고 코드짜버렸음 

이제 저거 물음표자리에 뭐 채워넣을지 고민하면 됩니다. 

 

둘 째 물음표는 어떤 내용으로 기존 document를 수정할지를 집어넣으면 되는데 

어떤 내용 ← 이건 요청.body 안에 담겨있지 않을까요 

그래서 요청.body 안에서 주섬주섬 꺼내서 채우면 될 것 같습니다. 

 

그럼 첫 물음표자리엔 어떤 document를 찾아서 수정할지 적으면 되는데

보통 _id로 찾는게 정확하기 때문에 

{_id : 어쩌구} 이런거 적으면 좋겠군요. 

어쩌구 자리에는 뭘 집어넣어요? 

아마 서버는 모르죠 그럼 어떻게해요?

손가락 빨면서 선생님의 코드를 기다립니다. 

 

 

 

 

그러면 안되고 서버에 없는 정보는 유저에게 보내라고 하거나

DB에서 뽑아보거나 둘 중 하나 하면 보통 해결됩니다.

"어떤 document를 수정하고 싶은지"는 서버는 모르고 유저만 알고있기 때문에 유저에게 보내라고 하면 됩니다. 

유저에게 수정할 글의 _id를 보내라고 합시다.

 

 

 

 

 

 

유저야 수정할 글의 _id도 보내라

 

(edit.ejs)

<form class="form-box" action="/edit" method="POST">
  (생략)
</form>

edit.ejs 파일 가보시면 <form>이 있는데

여기다가 <input>하나 만들어서 수정하고싶은 글 id도 보내라고 하면 되는거아닙니까

그러면 될거같습니다.

근데 유저가 천재도 아니고 id를 어떻게 기입하냐구요?

그럼 여러분들이 대신 써주면 되는거 아닙니까 

 

 

 

 

(edit.ejs)

<form class="form-box" action="/edit" method="POST">
  <h4>수정하기</h4>
  <input name="id" value="<%= result._id %>">
  <input name="title" value="<%= result.title %>">
  <input name="content" value="<%= result.content %>">
</form>

수정하고 싶은 글의 _id를 <input>에 집어넣으면 될 것 같습니다. 

실은 글의 _id는 근처의 result 변수안에 들어있는 것 같더라고요. 

 

 

 

app.post('/edit', async (요청, 응답)=>{
  await db.collection('post').updateOne({ _id : new ObjectId(요청.body.id) },
    {$set : { title : 요청.body.title, content : 요청.body.content }
  })
  응답.redirect('/list')
}) 

그래서 서버에선 요청.body.id 쓰면 출력해볼 수 있기 때문에

그걸로 첫 물음표를 잘 채워봤습니다. 

 

심심하시면 집에가서 안전하게 예외처리도 한번 해보도록 합시다. 

여러분들이 만든 API 기능을 악성유저처럼 한번 테스트해보면 되는데

- 유저가 글의 _id를 안보내면?

- 수정할 글을 보냈는데 글이 비어있으면?

- 글이 너무 길면? 

- DB에서 수정이 실패하고 에러가 나면?

 

등 여러가지 상황이 있겠군요. 

 

그리고 이런 것도 처리해주면 좋을거같은데 

지금 잘 보시면 유저가 <input name="id">내용을 막 만져버리면 어떻게 합니까 

그러면 안될거같죠? 이런건 숨겨놓는게 약간 더 안전할 거 같습니다. 

 

 

<input name="id"value="<%= result._id %>" style="display : none"> 

이런 스타일 주면 쉽게 숨길 수 있습니다. 

근데 이렇게 숨겨놔도 크롬 개발자도구 켜면 어짜피 다 보이고 맘대로 조작도 할 수 있습니다. 

그래서 프론트엔드에 있는건 모두 위조할 수 있기 때문에 

중요한 모든 데이터는 서버에서 검사해보는게 좋습니다. 

 

 

method override

 

야생으로 나가기 전에 <form>에서 PUT, DELETE 요청할 수 있는 법을 알아봅시다. 

수정, 삭제할 때 PUT, DELETE 같은거 쓰면 좀 이쁜 API를 만들 수 있다고 했으니까요. 

방법이 2개 있는데 

AJAX를 쓰거나 method를 강제로 변경해주는 라이브러리를 쓰거나 그러면 됩니다. 

후자를 써봅시다.

 

 

1. 터미널열어서 실행중인거 ctrl+c 눌러서 끄고

npm install method-override 입력해서 라이브러리 설치하고

 

 

2. 서버파일 상단에 이런 코드 추가합시다.

const methodOverride = require('method-override')
app.use(methodOverride('_method')) 

그럼 설치 셋팅 끝 

 

 

3. 그럼 이제 폼태그에서 요청을 날릴 때

<form action="/edit?_method=PUT" method="POST"> </form> 

이런 식으로 URL 가장 뒤에 ?_method=PUT 이런 식의 코드를 작성하면 

현재 폼전송시 POST요청이 아니라 PUT 요청으로 덮어쓰기가 됩니다. 

참고로 method="POST"는 냅둬야함 

그럼 이제 서버에서도 app.put 이렇게 쓰면 PUT 요청을 수신할 수 있습니다. 

그래서 쓰고 싶으면 씁시다.

 

 

 

 

 

updateOne 추가 문법

 

updateOne 자주쓸 수 있으니까 잠깐 추가문법같은거 몇개 배우고 지나갑시다.

그러기 위해서 임시로 document 하나를 DB 아무데나 발행해볼 것인데

대충 { like : 10 } 이런 식으로 숫자 하나만 기록해봅시다. 

어떤 글의 좋아요 갯수라고 칩시다. 

근데 이 좋아요 갯수를 가끔 +1 하고 싶을 때가 있을 텐데 어떻게 할지 알아봅시다. 

 

 

 

 

db.collection('컬렉션명').updateOne(
  { _id : new ObjectId('수정할 document _id') },
  { $set: { like : 1 } }
) 

이렇게 코드짜면 될 것 같은데 이러면 like 항목이 1로 변합니다. 

$set을 쓰면 기존 값을 덮어쓰기해줘서 그렇습니다. 

 

 

 

 

db.collection('컬렉션명').updateOne(
  { _id : new ObjectId('수정할 document _id') },
  { $inc: { like : 1 } }
) 

그게 싫으면 $inc 하면 되겠습니다

그러면 1로 덮어쓰기가 아니라 +1을 해줍니다. 

-1 적으면 -1 해줍니다. 

그래서 숫자 증감해주고 싶을 때는 $inc로 변경하면 되겠습니다.

 

(참고)

$mul 쓰면 기존값에 곱셈을 시켜줍니다. 

$unset 쓰면 기존에 있던 필드를 삭제해줍니다. 그러니까 like 항목 자체를 아예 제거해버림

근데 아예 삭제보다는 그냥 빈 문자열이나 0을 집어넣거나 하는 경우가 더 편할 수도 있어서 참고로만 알아두면 되겠습니다.

 

 

 

 

 

 

여러 document 동시 수정은 updateMany

 

동시에 여러개의 document를 수정하고 싶을 때도 어쩌다가 한 번 있습니다.

그럴 땐 updateOne 여러줄 써도 되긴하는데 귀찮으면 updateMany쓰면 편할 수 있습니다.

 

db.collection('컬렉션명').updateMany(
      { title : '멍청아' },
      { $set: { title : '착한친구야' } }
)

 

이렇게 코드짜면 이 컬렉션 안에 title이 '멍청아'로 되어있는 모든 document를 찾아서 

title 항목을 '착한친구야'로 덮어쓰기를 해줍니다. 

document 여러개 수정할 일 있으면 씁시다. 

당연히 세부 문법은 3일 후 까먹을테니 필요할 때 검색해서 씁시다. 

 

 

 

 

 

조건에 맞는 것만 updateMany하기

 

가끔가다가 정확히 이거랑 일치하는 document가 아니라

특정 조건을 만족하는 document를 전부 수정하고 싶을 때가 있습니다. 

 

예를 들어 like 숫자가 10이상인 document를 전부 찾아서 수정하고 싶은 경우도 있을텐데 

그런 경우는 어떻게 하냐면 $gt, $lt 연산자를 활용해서 조건문을 입력하면 됩니다. 

 

 

db.collection('컬렉션명').updateMany(
  { like : { $gt: 5 } },
  { $set: { like : 100 } }
) 

이렇게 작성하면 like 항목이 5보다 큰 document만 전부 필터링한 다음 

like 항목을 100으로 일괄 수정해버립니다. 

 

$gt 는 greater than의 약자라서 '초과'를 뜻하고

$gte 는 equal이 뒤에 붙었다고 생각하면 되어서 '이상'인걸 뜻합니다. 

$lt 는 lesser than의 약자라서 '미만'을 뜻하고

$lte 는 님들이 생각하는 그거 맞음 

$ne 쓰면 not equal이라는 뜻이라 예를 들어 like가 10이 아닌 것만 필터링해서 수정할 수도 있습니다. 

 

역시 따라적고 필기해봤자 어짜피 다 까먹기 때문에 의미없고 

그냥 이런 식으로도 가능하다라는것만 기억해두시고 

필요할 때 검색해서 씁시다. 

 

 

 

 

 

 

그래서 수정기능만들 때 새로 배운거 총정리하면

 

1. DB에있던 document 하나 수정하려면 db.collection().updateOne()

2. 수정할 때 $set연산자 쓰면 덮어쓰기, $inc는 기존에 있던 숫자를 원하는 만큼 증감 가능

3. 여러개 document 한 번에 수정하려면 updateMany 쓰면 되는데 거기서는 $gt같은걸로 특정조건을 만족하는 document만 필터링해서 수정해버릴 수도 있음

4. 서버에서 어떤 정보가 필요한데 근데 서버에서 찾을 수 없으면

유저에게 보내라고하거나 DB에서 출력해보거나 하면 되는거지 수동적으로 손가락 빨고있으면 안됩니다.

5. method override 사용하면 폼태그에서도 put, delete 요청 날릴 수 있는데 그럼 좀 더 이쁜 API를 만들 수 있음