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


2021년 7월 26일

지난 시간까지 Express의 기초와 pug를 사용한 HTML 코드 재사용에 대해 알아보았습니다. 이번 포스팅에서는 데이터베이스 파트로 들어가기 전에 데이터베이스와 서버의 연동이 어떤 느낌인지 연습해보는 시간을 갖도록 하겠습니다.

비디오별 페이지 구현🎬

비디오 페이지로 이동

우선 지난번에 만든 비디오 리스트를 수정해보겠습니다. 비디오 제목을 클릭하면 해당 비디오로 이동할 수 있게 만들겠습니다. 비디오 리스트를 만드는 틀은 mixins 폴더 안에 있는 video.pug 파일입니다. 비디오 제목마다 a 태그를 달아주겠습니다.

mixin video(video)
    div
        h4
            a(href=`/videos/${video.id}`)=video.title
        ul
            li #{video.rating}/5.
            li #{video.comments} comments.
            li Posted #{video.createdAt}.
            li #{video.views} views.

원래 h4 에는 video.title 만 들어갔지만 해당 비디오 페이지로 이동을 위해 a 태그를 만듭니다. 비디오 객체에서 해당 비디오의 id 값을 얻어와 URL에 추가합니다.

이제 비디오 제목을 누르면 해당 비디오 페이지로 이동할 수 있습니다.

비디오 페이지 타이틀 수정✍️

비디오 제목을 눌러 비디오 페이지로 이동한 모습입니다. 지금은 어떤 비디오를 눌러도 똑같은 제목에 똑같은 내용이 뜹니다. 페이지 타이틀은 비디오 타이틀로, 내용은 해당 비디오의 조회수로 바꿔보겠습니다. 우선 타이틀을 바꾸기 위해 videoContoller.js 파일의 see 컨트롤러 부분을 수정합니다.

export const watch = (req, res) => {
  const { id } = req.params;
  const video = videos[id - 1];
  res.render("watch", {
    pageTitle: `Watching: ${video.title}`,
  });
};

우선, watch.pug 파일과 연결되는 컨트롤러이므로 컨트롤러 이름을 watch 로 바꾸는 것이 나을 것 같습니다. videoRouter 파일에서도 이름을 수정해서 가져와야 합니다.

const { id } = req.params;

컨트롤러가 현재 비디오가 어떤 비디오인지 알게 하기 위해서 reqparams 에서 id 값을 가져옵니다. 사용자가 a 태그를 눌렀을 때 이동하게 되는 URL에 포함된 id 값입니다.

const video = videos[id - 1];

아직 데이터베이스가 없기 때문에 id 에 해당하는 비디오 객체를 얻는 과정은 임시로 만들었던 비디오 객체 배열을 통해 구현합니다. id 값은 첫 번째 비디오부터 순서대로 메겨저 있으므로 id - 1 을 통해 비디오의 인덱스를 구합니다.

res.render("watch", {
    pageTitle: `Watching: ${video.title}`,
  });

마지막으로 비디오 객체의 title 값을 pageTitle 에 적절히 넣습니다. 이제 비디오를 클릭해 들어가면 해당 비디오 제목이 타이틀로 나오게 됩니다.

비디오 조회수 표시하기👍

이제 저 watch videos! 부분에 조회수를 표시하겠습니다. 23 views 처럼 조회수를 표시하되, 만약 조회수가 1이라면 1view 처럼 단수형으로 표시해야 합니다. 우선 조회수 정보를 뽑아내야하기 때문에 videoControllerwatch 컨트롤러에 video 객체를 전달합니다.

export const watch = (req, res) => {
  const { id } = req.params;
  const video = videos[id - 1];
  res.render("watch", {
    pageTitle: `Watching: ${video.title}`,
    video, // here
  });
};

이제 video 객체를 watch.pug 파일에서 다룰 수 있습니다.

조건에 따라 viewviews 중 하나를 보여주는 기능은 자바스크립트의 삼항연산자를 사용하여 간단하게 구현할 수 있습니다. watch.pug 파일을 다음과 같이 수정합니다.

extends base.pug

block content
    h3 #{video.views} #{video.views === 1 ? "view" : "views"}
    a(href=`${video.id}/edit`) Edit Video →

먼저 video 객체의 views 값을 표시합니다. 그리고 만약 그 값이 1이라면 view 를, 아니라면 views 를 표시합니다. 이제 웹 페이지에 들어가보면 정상적으로 조회수가 표시됩니다.

89는 1이 아니므로 views 가 표시됩니다.

조회수가 1이면 view 가 표시됩니다.

비디오 수정 기능 구현

a 태그 만들기

이제 비디오 페이지로 이동해 edit 버튼을 누르면 비디오 제목을 수정할 수 있는 기능을 구현해보겠습니다. 우선 watch.pug 에 에딧 페이지로 갈 수 있는 a 태그를 만듭니다.

extends base

block content
    h3 #{video.views} #{video.views === 1 ? "view" : "views"}
    a(href=`${video.id}/edit`) Edit Video →

비디오 페이지 이동과 마찬가지로 비디오 객체의 id 값을 URL에 포함시킵니다. 이렇게 URL을 만들어주면 이전에 만들어두었던 videoRouter 가 에딧 페이지로 이동시켜줄 것입니다.

참고로 → 이라고 적힌 부분은 화살표 모양으로 렌더링됩니다.

Edit form 만들기📑

이제 edit.pug 파일에 비디오 제목을 수정할 수 있는 form 을 만들겠습니다. 그 전에 pug 파일에서 비디오 객체를 사용할 수 있도록 videoController.js 파일의 edit 컨트롤러에게 해당 id 의 비디오 객체를 전달합니다.

export const edit = (req, res) => {
  const { id } = req.params;
  const video = videos[id - 1];
  res.render("edit", {
    pageTitle: `Editing: ${video.title}`,
    video,
  });
};

그리고 pageTitle 에 들어갈 부분도 Editing: ${video.title} 로 수정합니다.

이제 edit.pug 파일에 다음과 같이 수정 form 을 만듭니다.

extends base

block content
    h4 Change Title of video
    form(method="POST")
        input(name="title", placeholder="Video Title", value=video.title, required, type="text")
        input(type="submit", value="save")

forminput에 들어가는 속성들은 HTML 기초에서 다루므로 생략하겠습니다. 다만 form 의 속성인 method의 종류에 대해서는 조금 알아둘 필요가 있습니다.

GET vs POST📮

사용자가 form 양식을 작성하고 제출 버튼을 누르면 폼 데이터가 HTTP로 전송됩니다. 이 데이터를 전송하는 방법이 getpost 로 나뉘는 것입니다. 둘의 특징을 정리하면 다음과 같습니다.

GET:

  • 폼 데이터가 URL 안에 name/value 쌍으로 포함됩니다.
  • URL의 길이는 3000 char로 제한됩니다.
  • URL에 데이터가 그대로 노출되므로 개인정보를 GET으로 전송하면 안 됩니다.
  • 구글 검색 같이 사용자가 검색한 페이지로 바로 이동하는 것에 적합합니다.

POST:

  • 데이터가 HTTP request body 안에 들어있습니다.
  • 사이즈 제한이 없습니다.
  • 데이터베이스나 서버에 데이터를 보낼 때 적합합니다.

비디오 타이틀을 수정하면 결국 데이터베이스에 반영될 것이므로 수정 form 은 데이터를 POST 방식으로 전송합니다. 여기까지 만들고 제출 버튼을 누르면 브라우저에서 오류 메시지를 보여줍니다.

라우터 수정

지금까지 만든 서버는 POST 요청에 응답하는 방법을 모르기 때문입니다. videoRouter.js 파일에서 /:id/edit URL의 POST 요청에 응답할 수 있도록 해줍니다.

videoRouter.get("/:id/edit", edit);
videoRouter.post("/:id/edit", postEdit);

기존에 있던 edit 페이지의 get 밑에 post 를 달아줍니다. 이렇게 경로가 같을 경우 둘을 합쳐서 반복되는 코드를 줄일 수 있습니다.

videoRouter.route("/:id/edit").get(edit).post(postEdit);

컨트롤러 수정

컨트롤러도 조금 손봐줘야 합니다. 우선, edit 컨트롤러는 하나밖에 없으므로 컨트롤러를 getEditpostEdit 으로 나눠줍니다.

export const getEdit = (req, res) => {
  const { id } = req.params;
  const video = videos[id - 1];
  res.render("edit", {
    pageTitle: `Editing: ${video.title}`,
    video,
  });
};

export const postEdit = (req, res) => {};

videoRouter.js 파일 위의 import 문도 같은 이름으로 수정해줍니다.

이제 비디오 수정 기능이 작동할 수 있게끔 postEdit 컨트롤러를 만들어줍니다.

export const getEdit = (req, res) => {
  const { id } = req.params;
  const video = videos[id - 1];
  res.render("edit", {
    pageTitle: `Editing: ${video.title}`,
    video,
  });
};

export const postEdit = (req, res) => {
  const { id } = req.params;
  const { title } = req.body;
  videos[id - 1].title = title;
  return res.redirect(`/videos/${id}`);
};

아직 데이터베이스가 없기 때문에 videos 배열의 해당 비디오의 title 을 직접 수정해줍니다. 물론 이렇게 수정된 제목은 페이지를 새로고침하면 원래대로 돌아옵니다. 타이틀 변경을 마치고 해당 비디오 페이지로 redirect 합니다.

urlencoded()

이제 pug파일, 라우터 파일, 컨트롤러 파일에 비디오 수정 기능 구현을 위한 작업을 모두 마쳤습니다. 하지만 작업 하나를 더 해줘야 합니다. 사용자가 폼 데이터를 담은 HTTP 리퀘스트를 보내면 Express가 해당 리퀘스트의 body를 파싱할 수 있어야 합니다. 익스프레스에서는 urlencoded() 라는 내장 미들웨어를 제공합니다.

server.js 파일에 해당 미들웨어를 적용해줍니다.

app.set("view engine", "pug");
app.set("views", process.cwd() + "/src/views");
app.use(morgan("dev"));
app.use(express.urlencoded({ extended: true })); // here
app.use("/", globalRouter);
app.use("/videos", videoRouter);
app.use("/users", userRouter);

비디오 업로드 기능 구현📽

마지막으로, 비디오를 업로드하는 기능을 구현해보겠습니다. 업로드 기능 역시 데이터베이스가 없는 상태에서 간단하게 느낌만 내보겠습니다.

네비게이션 만들기

업로드 버튼은 nav 안에다 만들겠습니다. base.pug 파일의 header 안에 nav 태그를 만듭니다.

doctype html
html(lang="ko")
    head
        link(rel="stylesheet" href="https://unpkg.com/mvp.css")
        title #{pageTitle} | Wetube
    body
        header
            h1=pageTitle
            nav
                ul
                    li
                        a(href="videos/upload") Upload Video
        main
            block content
        include partials/footer.pug

upload.pug 만들기

a 태그를 누르면 이동할 페이지를 만듭니다. 비디오 제목을 입력하고 제출할 수 있는 폼을 만들어줍니다.

extends base

block content
    form(method="POST")
        input(name="title", placeholder="Title", required, type="text")
        input(type="submit", value="Upload video")

폼과 관련된 내용은 비디오 수정 기능 구현 때 만들었던 폼과 동일합니다.

컨트롤러 수정

이제 videoController.js 파일에서 컨트롤러를 수정합니다.

export const getUpload = (req, res) => {
  return res.render("upload");
};

export const postUpload = (req, res) => {
  const { title } = req.body;
  const newVideo = {
    title,
    rating: 0,
    comments: 0,
    createdAt: "Just now",
    views: 0,
    id: videos.length + 1,
  };
  videos.push(newVideo);
  return res.redirect("/");
};

컨트롤러의 구분을 위하여 기존 upload 컨트롤러의 이름을 getUpload 로 바꿉니다. 그리고 postUpload 컨트롤러를 새로 만듭니다.

postUpload 역시 HTTP body에서 폼에 입력한 비디오 제목을 가져옵니다.

const { title } = req.body;

그리고 기존 비디오 객체 배열에 추가할 새 비디오 객체를 만듭니다.

const newVideo = {
    title,
    rating: 0,
    comments: 0,
    createdAt: "Just now",
    views: 0,
    id: videos.length + 1,
  };

title 만 입력값을 받아오고 나머지 요소들은 임으로 정합니다. id 에는 현재 비디오 배열의 길이에 1을 더한 값을 넣습니다(id가 1부터 시작하기 때문에).

videos.push(newVideo);
return res.redirect("/");

새로 만들어진 newVideo 객체를 videos 배열 뒤에 추가하고 사용자를 홈으로 redirect 시킵니다.

POST 요청 처리

videoRouter.js 에서 위에서 만든 두 컨트롤러를 import 합니다. 그리고 비디오 수정 때와 같은 방식으로 getpost 요청을 /upload URL로 받습니다.

videoRouter.route("/upload").get(getUpload).post(postUpload);

이제 홈 페이지에서 Upload Video 를 누르고 들어가서 비디오 제목을 입력하면 새로 만들어진 비디오 객체가 정상적으로 비디오 배열에 추가된 것을 볼 수 있습니다.

이번 포스팅에서는 지금까지 배운 기초 지식을 기반으로 서버와 데이터베이스의 소통을 가볍게나마 연습해보았습니다. 다음 포스팅에서는 MongoDB의 기초에 대해 배워보겠습니다.


<참고 문서>

좋은 웹페이지 즐겨찾기