Node.js

node>상세페이지 만들기 (URL parameter,링크 만들기)

연습노트 2024. 7. 25. 08:13

게시판의 경우에 글제목 누르면 상세페이지 같은걸로 이동하지 않습니까 

그래서 우리도 글마다 상세페이지를 만들어봅시다.

한글로 기능설명부터하면 되는데

근데 내가 그 기능이 어떻게 돌아가는지 몰라서 한글로 설명도 못하겠으면  

다른 사이트의 상세페이지 기능은 어떻게 돌아가고 있는지 살펴보면 됩니다. 

 

 

 

 

네이버 Vibe라는 음악감상하는 사이트인데 

곡을 누르면 곡마다 상세페이지가 있습니다.  

근데 URL 주목해보시면 

 

/track/곡번호 

 

이렇게 접속하면 거기에 맞는 곡의 상세페이지를 보여주는 식으로 만들어놨군요. 

여기말고도 다른 서비스의 상세페이지들도 다 비슷하게 동작할걸요.

그래서 우리도 요 시스템을 카피해보도록 합시다.

 

누가 /detail/글번호로 접속하면 그 글번호를 가진 글의 상세페이지를 보여줍시다. 

자 지금 DB에 글이 3개 있는데 그럼 상세페이지는 몇개 필요하죠?

3개 필요합니다.

그럼 여러분이 직접 ejs파일도 3개 만들고 app.get() 3번써가지고 각각 상세페이지들을 만들면 될 것 같습니다. 

 

예를 들면

유저가 /detail/1 로 접속하면 _id가 1인 글의 상세페이지 보여주기

유저가 /detail/2 로 접속하면 _id가 2인 글의 상세페이지 보여주기 

...

실제로는 _id가 정수가 아니라 ObjectId 형태라 좀 길긴 하겠지만 아무튼 그럽시다.

 

 

 

 

 

 

URL 파라미터 문법

 

근데 문제가 있는데 만약에 글이 100개 있으면 어쩔겁니까?

app.get( ) 100개 만들거에요? 

실은 그래도 되는데 그게 싫으면 방법이 하나 있습니다. 

URL 파라미터 문법을 이용하면 비슷한 URL을 가진 API를 여러개 만들 필요가 없습니다. 

 

 

app.get('/detail/:aaaa', (요청, 응답) => {
  응답.send('detail.ejs')
})

URL 입력란에 :어쩌구 이런 식으로 URL을 작성할 수가 있는데 

이게 뭔 뜻이냐면 "이 자리에 유저가 아무 문자나 입력하면~" 이라는 뜻입니다.

그래서 이제 누가 /detail/아무문자 로 접속하면 이 안에 있는 코드가 실행됩니다.

이러면 아까처럼 API 100개 만들필요가 없으니 비슷한 URL의 API가 여러개 필요하면 가져다가 쓰도록 합시다. 

 

 

그래서 detail.ejs 페이지도 하나 만들어서 보내주면 될거같은데 

Q. 이러면 계속 같은 페이지만 보여줄 수 있는거 아님?

그럴 수 있습니다.

지금 유저가 /detail/1 로 접속해도 detail.ejs 보내주고

지금 유저가 /detail/2 로 접속해도 detail.ejs 보내주기 때문에 이러면 안될 것 같은데

실은 ejs 파일로 여러분이 맘대로 데이터를 전송할 수 있기 때문에

매번 똑같은 페이지만 보일 걱정은 안해도 될 것 같습니다.

 

 

 

 

 

 

 

detail.ejs 파일 만들기

 

상세페이지로 쓸 detail.ejs 파일도 하나 만들어봅시다. 

 

detail.ejs 레이아웃

 

기존 ejs 페이지 복사해서 필요한 곳만 고칩시다. 

 

<div class="detail-bg">
    <h4>글제목임</h4>
    <p>글내용임</p>
</div> 

 

.detail-bg {
  background: white;
  padding: 15px;
  margin-top: 10px;
}

 

 

[collapse]

 

 

그럼 만들고 싶은 기능을 한글로 정리부터 해보면 

1. 누가 /detail/어쩌구로 접속하면

2. _id가 어쩌구인 글을 DB에서 찾아와서 

3. ejs에 글을 박아서 유저에게 보냄

 

이러면 될 것 같은데 1번은 아까 해본 거 같고  

2번부터 해봅시다. 

 

누가 /detail/abc 라고 입력해서 접속하면 

_id가 abc인 글을 찾아오면 될거같은데

DB에서 _id가 abc인걸 찾고싶으면 어떻게 하냐고요? 

 

 

 

 

 

 

DB에서 특정 document 1개 찾기

 

await db.collection().findOne({a : 1}) 

이렇게 쓰면 이 자리에 a : 1이라는걸 가지고 있는 document를 하나 찾아서 출력해준다는군요. 

a : 1을 가진게 많으면 그 중에 맨 위에 있는 document 한개만 출력해줍니다. 

 

근데 지금 a : 1 기입된 document 찾고싶은게 아니라

우리는 { _id : 어쩌구 } 인 document를 찾고싶은데 이거 잘 되나 한번 테스트해봅시다.

 

 

 

 

▲ 대충 이런 document를 _id로 찾고싶으면 코드를 어떻게 짜야할까요? 

 

 

await db.collection('post').findOne({_id : new ObjectId('64bfde3b02d2932a4c06ffba')}) 

이렇게 작성하면 이 자리에 document를 출력해줄 것 같습니다. 진짠지 변수에 저장해서 출력해봅시다. 

new는 왜 붙였냐면 mongodb 만든 사람이 그렇게 쓰래요.

근데 실은 서버파일에서 ObjectId() 를 쓰려면 셋팅 하나가 필요합니다. 

 

 

const { ObjectId } = require('mongodb') 

서버파일 상단 쯤에 위에 이거 집어넣어놔야 하단에서 ObjectId()를 쓸 수 있습니다. 

 

 

 

 

 

유저가 URL 입력한거 가져오기 

 

1. 누가 /detail/어쩌구로 접속하면

2. _id가 어쩌구인 글을 DB에서 찾아와서 

3. ejs에 글을 박아서 유저에게 보냄

 

이제 이거 코드로 번역하면 되는데 

일단 유저가 /detail/어쩌구 로 접속하면

{ _id : 어쩌구 } 를 가지고 있는 document를 찾아야합니다. 

그니까 { _id : 유저가URL 파라미터에입력한거 } 이런 document를 찾아와야합니다. 

"유저가 URL 파라미터에 입력한거" 이건 어떻게 알 수 있냐고요? 

 

 

 

 

app.get('/detail/:aaaa', (요청, 응답) => {
  console.log(요청.params)
})

요청.params라고 출력해보면 유저가 URL 파라미터 자리에 입력한 데이터가 출력됩니다.

아마 object 자료형으로 출력될걸요 

 

 

 

1. 누가 /detail/어쩌구로 접속하면

2. { _id : 어쩌구 } 인 글을 DB에서 찾아와서

3. detail.ejs 파일에 집어넣어서 유저에게 보냄 

이걸 코드로 옮겨보면 

 

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

이거 아닐까요 

URL파라미터 사용시 : 콜론기호 뒤엔 자유롭게 작명가능합니다. 

 

 

(detail.ejs)
<div class="detail-bg"> 
  <h4><%= result.title %></h4> 
  <p>글내용임</p> 
</div>

이제 detail.ejs 파일에선 result라고 쓰면 그 document 내용이 잘 출력될 것 같습니다. 

이제 테스트 삼아서 /detail/DB에있던글_id 로 한번 접속해보시길 바랍니다. 

그럼 페이지 내용이 잘 출력되는군요.

 

 

 

 

 

 

링크만들기 

 

그래서 아무튼 이제 /detail/DB에있던글_id 로 접속하면 

그 글의 상세페이지를 보여주고 있는데 의문점이 하나 듭니다. 

유저들이 천재도 아니고 이거 글_id를 주소창에 어떻게 입력하죠?

 

실은 링크라는게 좋은게 있습니다.

누르면 자동으로 /detail/DB에있던글_id로 GET요청되는 링크나 버튼 만들어두면 되는거 아닙니까.

그래서 list페이지에 링크들을 만들어보도록 합시다. 

 

 

 

 

 

<a href="/어쩌구">클릭</a>

<a>태그를 쓰면 링크를 만들 수 있고 그거 누르면 /어쩌구라는 URL로 페이지가 이동됩니다. 

 

 

 

 

(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>
      </h4>
      <p>글내용임</p>
    </div>
  <% } %>
</div>

그래서 href=" " 안에 글의 _id를 집어넣어봤습니다.

글의 _id는 글목록이라는 변수 출력해보면 나올텐데

거기서 뽑아서 집어넣어봤습니다. 

 

클릭하면 이동 잘 되나 확인해봅시다. 

참고로 링크들에 밑줄쳐있는게 아니꼬우면 

 a태그에 text-decoration : none 스타일을 줍시다. 

 

 

 

 

 

 

예외상황 처리하기

 

여러분이 어떤 서버기능을 하나 만들었으면

예외상황들에 대처해주는 코드도 넣는게 좋습니다. 

 

예를 들어 유저가 /detail/글_id를 입력하는게 아니라

이상한걸 /detail/바보 라고 입력해서 서버로 요청을 날리면 어떻게 될까요? 

한번 시도해봅시다.

아마 터미널에 에러메세지가 뜰걸요. 

 

 

 

 

app.get('/detail/:id', async (요청, 응답) => {
  try {
    let result = await db.collection('post').findOne({ _id : new ObjectId(요청.params.id) })
    응답.render('detail.ejs', { result : result })
  } catch (){
    응답.send('이상한거 넣지마라')
  }
  
})

그래서 그런 상황을 막고싶으면 예외처리하면 되는데

에러를 막고 싶으면 try catch 안에 넣으면 됩니다. 

 

 

근데 이렇게 해놓으면 잘 될 것 같은데

제가 한번 에러를 회피해보도록 하겠습니다.

에러메세지 잘 읽어보면 ObjectId() 안에 들어갈 문자가 너무 짧다는 에러같은데

그럼 /detail/적절한길이의랜덤문자 로 접속하면 어떻게 될까요? 

 

 

 

 

▲ URL에 24자의 이상한 문자를 기입했더니 이번엔 ejs 파일에서 에러가 났다는군요. 

null에다가 .title을 붙일 수 없다는 소리같습니다. 

그래서 다양한 상황을 직접 테스트해보는게 중요합니다. 

 

이런 상황에서 result 변수같은걸 출력해보면 null이 나오는데 

(null은 텅 비었다는걸 나타내는 자료형입니다)

만약에 db에서 찾은 게시물이 null 이면 메세지 보내라고 if문 같은거 쓰면 될 것 같습니다. 

 

 

 

 

app.get('/detail/:id', async (요청, 응답) => {
  try {
    let result = await db.collection('post').findOne({ _id : new ObjectId(요청.params.id) })
    if (result == null) {
      응답.status(400).send('그런 글 없음')
    } else {
      응답.render('detail.ejs', { result : result })
    }
    
  } catch (){
    응답.send('이상한거 넣지마라')
  }
  
})

if문을 추가해봤습니다. 

그리고 예외상황에선 에러코드같은것도 보내주면

프론트엔드에서 유저가 어떤 문제인지 파악하기 쉬운데 

유저 잘못으로 에러가 났을 경우엔 4XX 같은 코드를 .status() 안에 넣어주면 되고

서버 잘못으로 에러가 났을 경우엔 5XX 같은 코드를 .status() 안에 넣어주면 됩니다.

안한다고 뭐 나쁜건 없는데 프론트엔드에서 서버와 통신할 때 에러원인 찾는게 쉬워집니다.

 

 

https://developer.mozilla.org/en-US/docs/Web/HTTP/Status

더 정확히 하고싶으면 status code 목록같은게 있는데 

이런거 보고 정확히 기재하셔도 좋습니다. 

 

아무튼 결론은 굳이 저 따라안해도 

여러분들이 직접 악성 유저가 되어서 테스트해보면 금방 알 수 있습니다.

 

 

그래서 저번강의 이번강의 배운거 정리하면 

1. URL 파라미터 문법 이용하면 비슷한 URL가진 API 여러개 만들 필요가 없음 

2. db에서 게시물 하나만 찾아오려면 db.collection().findOne({ })

3. 여러분들이 직접 악성유저가 되어서 이거저거 테스트해보고 그걸 다 예외처리하면

더 안정적인 서버기능을 만들 수 있습니다.