유튜브 클론코딩 복습노트-7

Hyuno Choi·2021년 8월 5일
0
post-thumbnail

2021년 8월 3일

지금까지 만든 웹 페이지는 비디오를 업로드 할 때 다음과 같은 정보를 입력하도록 했습니다. 물론 기초를 잡기 위해 임시로 만든 업로드 폼이며, 본격적인 클론 코딩을 시작하면 좀 더 세련되게 다듬을 예정입니다.

  • 비디오 제목(title)
  • 비디오 설명(description)
  • 해쉬태그(hashtags)

해쉬태그의 경우 나머지 두 개의 입력값보다 복잡한 처리를 거쳤습니다. 최초 업로드와 기존 데이터 수정의 경우에 모두 적용해주었는데요,

  • 사용자가 해쉬 태그에 # 를 붙이면(예를 들어 #바다) => 그대로 DB에 적용
  • 사용자가 해쉬태그에 # 를 안 붙이면(예를 들어 바다) => # 를 붙여 DB에 적용

이런 처리를 다음과 같은 자바스크립트의 삼항 연산자로 구현해주었습니다.


여담으로, 이제부터 포스팅에 코드를 첨부할 때 비쥬얼 스튜디오 코드의 PolaCode 라는 익스텐션을 사용하기로 했습니다.😉 돌이켜보면,

  1. Carbon
  2. Markdown 코드블럭
  3. PolaCode

순서대로 포스팅에 소스코드를 넣는 방법이 변했습니다. Carbon은 사진 크기가 너무 크고, Markdown은 문법 강조 기능이 부족해서 아쉬웠는데 PolaCode는 앞으로 오래 사용할 것 같은 기분이 듭니다.


다시 삼항 연산자 이야기로 돌아와서, 저 코드는 총 두 번을 반복하여 써야 합니다. 첫 번째는 업로드 때, 두 번째는 수정 때입니다. 따라서 코드 재사용성을 높이기 위해 고안할 수 있는 첫 번째 방법은 해쉬태그 문자열을 처리하는 함수를 만들어 업로드와 수정 페이지 랜더를 담당하는 컨트롤러에게 넘겨주는 것입니다. 그러나 mongoose를 사용하고 있기 때문에 더 좋은 방법을 사용할 수 있습니다.

mongoose static 함수 사용

몽구스에서는 각 스키마에 static() 을 통해 함수를 달아줄 수 있습니다. 예를 들어, videoSchema에 함수를 달아주려면 videoSchema.static("함수이름", 함수) 와 같이 사용하면 됩니다. 이렇게 스키마에 함수를 달아주면 나중에 Video 모델을 활용해 Video.함수이름() 과 같이 함수에 접근할 수 있게 됩니다. videoController.js 파일에서는 이미 Video 모델을 import 하고 있으므로, 훨씬 간편하게 해쉬태그 처리를 구현할 수 있을 것입니다.

Video.js 파일에서 videoSchema를 정의한 코드 밑에 다음과 같이 작성합니다.

함수 이름은 formatHashtags 로 설정합니다. 나중에 Video.formatHashtags 와 같이 함수에 접근할 수 있게 됩니다. 그리고 함수를 이전과 동일한 논리로 해쉬태그를 처리할 수 있게 정의합니다.

이제 videoController.js 파일에 있는 postUploadpostEdit 컨트롤러에 작업을 해줍니다. 원래 있던 해쉬태그 문자열 처리 부분을 삭제하고 다음과 같이 Video 모델에서 위에서 만든 함수에 접근합니다. 비디오 document를 생성하는 부분만 가져왔습니다.

사용자가 입력한 hashtags 데이터는 req.body 에서 파싱되어 formatHashtags() 함수를 거쳐 데이터베이스에 저장됩니다. 이제 해쉬태그 문자열을 처리하는 부분을 다듬었으니, 홈 페이지와 시청 페이지에서 비디오 정보와 함께 해쉬태그도 함께 보여주도록 하겠습니다. 비디오 리스트 안에서 각각의 비디오 객체의 정보를 화면에 표시하는 것은 video mixin이 하는 역할이므로 /mixins/video.pug를 수정합니다.

다른 부분은 이전 코드와 동일합니다. p 태그로 이루어진 비디오 설명 밑에 pug 반복문을 사용해서 해쉬태그들을 리스트로 배열합니다. 홈 페이지에 들어가보면 사용자가 입력한 해쉬태그들이 정상적으로 보입니다.

비디오 삭제 기능 구현

pug 파일 수정

이제 각각의 비디오 시청 페이지에 '비디오 삭제' 버튼을 만들고 DB에서 비디오를 삭제할 수 있는 기능을 구현해보겠습니다. 우선, 제일 간단한 것부터 시작해보겠습니다. 홈 페이지에서 비디오 제목을 누르면 가게 되는 watch.pug 파일에 삭제 버튼을 달아줍니다.

/delete 요청 처리 구현

맨 밑에 a 태그로 삭제 버튼(링크)을 만들어주었습니다. 사용자가 이 버튼을 누르면 /videos/비디오ID/delete 요청을 서버에 보내게 됩니다. 이 요청을 videoRouter.js 파일에서 잡아주도록 하겠습니다. 해당 URl의 get 요청을 처리하는 코드를 마지막 줄에 작성해줍니다.

그리고 videoRouter.jsimport 구문에 getDelete 를 추가합니다. 이제 아직 존재하지 않는 getDelete 컨트롤러를 videoController.js 파일에 만들어줍니다.

getDelete 컨트롤러 구현

사용자가 삭제 버튼을 누르면 해당 비디오가 DB에서도 사라져야 합니다. 삭제 기능은 /video/비디오ID/delete 요청으로 서버에 보내지므로 URL에 포함된 비디오 ID 파라미터를 통해 비디오 데이터의 ID를 알 수 있습니다. 따라서 몽구스의 findByIdAndDelete() API를 사용해 간단하게 DB에서 해당 비디오를 삭제할 수 있습니다.

req.params 에서 URL에 포함된 비디오 ID를 얻어옵니다. 그리고 ID를 기반으로 DB에서 동영상을 검색해 삭제합니다. 이 역시 외부 DB 서버에서 이루어지는 작업이므로 await 를 사용해 삭제를 기다려줍니다. 삭제가 끝나면 사용자를 홈 페이지로 리디렉트합니다.

비디오 검색 기능 구현

지금까지 업로드, 수정, 삭제 기능까지 구현했습니다. 마지막으로 키워드를 통해 DB에서 비디오 데이터를 검색할 수 있는 기능을 구현해보겠습니다. 검색 기능을 구현하는 순서는 다음과 같습니다.

  1. pug 파일을 통해 기존 base.pug 를 확장하여 검색 페이지를 만듭니다.
  2. basenav 태그 내부에 /search 로 이동할 수 있는 a 태그를 만듭니다.
  3. /search 요청을 globalRouter에서 받습니다.
  4. 요청 처리를 담당하는 컨트롤러를 videoController.js 파일에 만듭니다.

search.pug 파일 만들기

search.pug 파일에는 다음과 같은 내용이 들어가야 합니다.

  • 검색어를 입력할 수 있는 폼
  • 검색 결과를 나열할 수 있는 리스트

우선 extends 를 사용하여 base.pug 를 템플릿으로 확장합니다. 검색된 비디오를 나열할 때 비디오 정보를 표시하는 양식은 mixins/video.pug 에 만들어두었으므로 include를 통해 불러옵니다.

그리고 basecontent 블럭에 검색 키워드를 입력하고 제출할 수 있는 폼을 만듭니니다. 키워드와 매치되는 검색 결과는 여러개일 수 있으므로 pug의 반복문을 사용하여 비디오 데이터 배열에서 비디오 데이터를 하나씩 가져와 video mixin에 대입합니다.

검색 페이지 이동 버튼 만들기

상대적으로 가벼운 작업입니다. base.pugnav 태그 내부 리스트에 검색 페이지로 가는 버튼을 만들어줍니다. 이왕 만들어주는 김에 홈으로 돌아가는 버튼도 만들겠습니다.

/search 요청 받기

이제 사용자가 비디오 검색 버튼을 누르면 /search 요청이 서버로 들어옵니다. globalRouter 에서 그 요청을 받도록 하겠습니다. globalRouter.get("/search", search); 를 이용해서 요청을 받습니다.

이렇게 해서 글로벌 라우터가 처리하는 경로는 총 4개가 되었습니다. /search 요청은 search 컨트롤러로 처리할 예정이므로 글로벌 라우터의 import 문에서 search 를 추가로 가져옵니다.

search 컨트롤러 만들기

컨트롤러 뼈대

이제 마지막으로 가장 중요한 컨트롤러를 만들어주겠습니다. 비디오 검색 페이지를 담당하는 컨트롤러는 다음과 같은 기능을 해야 합니다.

  • 검색 페이지 렌더
  • 검색 키워드를 바탕으로 DB안에서 비디오 검색
  • 검색 결과 화면에 보여주기

우선 사용자가 검색 페이지에서 제출한 폼에서 검색 키워드를 받아와야 합니다. const { keyword } = req.query; 로 사용자가 입력한 키워드를 변수에 저장합니다.

지금까지 POST 방식을 사용하는 비디오 업로드나 수정 폼에서는 데이터를 req.body 에서 얻어왔습니다. 이는 포스트 요청으로 보낸 데이터가 HTTP body 안에 포함되기 때문입니다. GET 요청에서는 사용자의 입력값이 namevalue 의 쌍으로 URL 안에 쿼리 형태로 들어가기 때문에 req.query 에서 데이터를 가져옵니다. console.log(keyword); 를 통해 검색 버튼을 눌렀을 때 keyword 가 가져오는 값을 확인해보면 검색어를 잘 가지고 오는 것을 확인할 수 있습니다.

이제 검색 키워드를 바탕으로 비디오를 찾아 videos 배열에 넣어주겠습니다. DB에서 비디오를 찾는 기능은 mongoose의 Model.find() API를 통해 구현합니다. find() 는 쿼리를 인자로 받습니다. 비디오 제목을 기준으로 검색하게 할 것이므로 title 을 쿼리로 넘겨줍니다.

정규표현식 사용

그러나 {title: keyword} 와 같이 키워드를 바로 타이틀에 연결하는 건 그리 세련된 방법이 아닙니다. 만약 이런 방법을 사용한다면 비디오 제목을 풀네임으로 입력해야만 해당 비디오가 검색될 것입니다.

제목의 일부만 입력해도 검색될 수 있게 정규표현식을 사용하겠습니다. 몽고DB는 Evaluation Query Operators를 지원하는데, 그중 쿼리 안에 정규식을 사용할 수 있는 연산자가 $regex 입니다. {title: {$regex: 정규표현식}} 과 같이 title을 정규표현식 패턴으로 검색할 수 있습니다.

자바스크립트에서 정규표현식을 사용하는 방법은 두 가지입니다. 하나는 literal notation 이고 다른 하나는 constructor funtion 입니다.

  1. literal notation: /ab+c/i
  2. constructor function: new RegExp('ab+c', 'i')

literal notation 같은 경우 정규식이 한 번 컴파일 된 이후에는 다시 컴파일되지 않습니다. 즉, 사용자가 어떤 검색어를 입력할지 모르는 상황에서 사용하기에는 적합하지 않습니다.

constructor function 을 통한 방법은 RegExp()의 첫 번째 인자인 문자열을 변수로 처리해 정규식을 상황에 맞게 변경할 수 있습니다. 참고로 두 번째 인자인 i 는 대문자와 소문자를 구분하지 않겠다는 뜻입니다.

이제 constructor function 을 사용해 정규식을 쿼리 안에 만들어주겠습니다.

이제 사용자가 입력한 키워드가 비디오 제목 어디에 위치하고 있던 검색이 가능합니다. first videosecond video 가 있을 때 video만 입력하면 두 비디오 데이터가 모두 검색됩니다.

비디오 정렬

보이는 것처럼 홈 페이지에 비디오가 먼저 만든 순서대로 나옵니다. 일반적이라면 최신 비디오가 제일 상단에 위치해야 하므로 홈 페이지에서 비디오를 DB로부터 가져올 때 정렬 순서를 바꿔주도록 하겠습니다. 홈 페이지를 담당하는 컨트롤러 이름은 trending 입니다. 이왕 건드리는 김에 컨트롤러 이름도 home 으로 바꾸겠습니다. globalRouter 에 있는 이름도 바꿔줍니다.

home 컨트롤러는 mongoose의 Model.find() API에 빈 객체를 넘겨 비디오 데이터를 전부 불러옵니다. 데이터 정렬은 find() 쿼리의 메소드인 sort() 를 사용해 구현합니다. sort() 는 객체를 인자로 받습니다. 정렬 대상이 되는 스키마와 정렬 방법을 쌍으로 주면 됩니다. 지원하는 정렬 순서는 다음과 같습니다.

  • 오름차순: asc, ascending, 1
  • 내림차순: desc, descending, -1

저는 만들어진 시간의 역순으로 배열하고 싶으므로 {createdAt: "desc"} 을 인자로 넘겨줍니다.

이제 홈 화면에서 비디오들이 최신 순서대로 정렬됩니다.

지금까지 프로젝트의 기본 틀을 구현하고 데이터베이스의 CRUD를 구현해보았습니다. 다음 포스팅부터는 로그인 및 유저 인증에 대해 다루어보겠습니다.🔐


<참고 문서>

profile
프론트엔드 웹 개발자를 목표로 하고 있습니다.

0개의 댓글