일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 제어유닛
- 자바스크립트
- MongoDB
- 자바스크립트 배열
- mongoose
- 유튜브클론코딩
- Session
- 깃허브 로그인
- Nodemon
- 프론트엔드
- node.js
- 유튜브 클론코딩
- react
- 보조저장장치
- CPU
- 후위표기법연산
- 자료구조
- Express
- 후위표기법변환
- cookie
- 리액트
- 모던 자바스크립트 Deep Dive
- 마이크로명령어
- 제어유니트
- 표현식과 문
- 컴퓨터 구조
- pug
- 컴퓨터 구조론
- 연결자료구조
- JavaScript
- Today
- Total
909 Devlog
[유튜브 클론코딩] API 만들기 본문
이때까지 서버에게 어떤 동작을 하도록 시키려면, URL을 입력하거나 링크를 타고 페이지에 들어가 get 요청을 보내거나, form의 post를 통해 서버의 controller를 통해 백엔드를 동작시켜 응답을 받았습니다.
하지만 URL과 form을 사용하지 않고 서버의 controller를 동작시키고 싶을 때가 많습니다. 유튜브를 예로 들면 영상 조회수를 증가시키거나, 좋아요 / 싫어요, 재생목록 추가 기능들은 URL과 form을 사용하지 않고 동작합니다.
따라서, 이번 포스팅에서는 URL과 form을 사용하지 않고 서버의 controller를 작동시키는 법을 알아보겠습니다.
만들어 볼 기능은 페이지에 올라와있는 영상을 다 보면 조회수가 올라가도록 하는 기능입니다.
1. api 기본 세팅
api를 사용할 라우터 파일을 만들어 줍니다.
apiRouter.js에 다음과 같이 상수 apiRouter를 express router라고 선언하고 export 해줍니다.
// apiRouter.js
import express from "express";
const apiRouter = express.Router();
export default apiRouter;
apiRouter를 사용할 서버 파일인 server.js에서 apiRouter를 import 하고 사용하는 코드를 작성합니다.
// server.js
...
import apiRouter from "./routers/apiRouter";
...
app.use("/api", apiRouter);
...
videoController.js 파일에 api 요청에 대응할 registerView controller를 만들었고,
// videoController.js
export const registerView = (req, res) => {};
apiRouter.js에 post 요청을 보낼 URL와 그에 대응할 registerView controller를 불러와서 적용시켰습니다.
// apiRouter.js
import express from "express";
import { registerView } from "../controllers/videoController";
const apiRouter = express.Router();
apiRouter.post("/videos/:id([0-9a-f]{24})/view", registerView);
export default apiRouter;
2. api에 사용할 controller 작성, 사용
registerView controller를 다음과 같이 변경했습니다.
export const registerView = async (req, res) => {
const { id } = req.params;
const video = await VideoModel.findById(id);
if (!video) {
return res.status(404);
}
video.meta.views += 1;
await video.save();
return res.status(200);
};
registerView contoller가 동작하게 되면, URL에 있는 id와 같은 id를 가진 영상 데이터를 찾습니다.
영상을 찾지 못하면 http status code 404를 보내고, 영상을 찾았다면 해당 영상 데이터의 meta - views를 1 증가시키고, 변경사항을 저장한 뒤 http status code 200를 보냅니다.
이제 해당 controller를 동작하게 할 트리거를 만들어 봅시다.
프론트엔드에서 사용하는 자바스크립트 파일인 src - client - js - videoPlayer.js 파일에 URL로 요청하는 이벤트를 작성해 보겠습니다.
// videoPlayer.js
const video = document.querySelector("video");
const handleEnded = () => {
fetch("/api/videos/{해당 영상의 id}/view")
};
video.addEventListener("ended", handleEnded);
페이지에 렌더링된 비디오를 querySelector로 선언하고, 영상 재생이 끝났을 때 hadleEnded 함수가 작동하는 코드를 작성했습니다.
handleEnded 함수를 보면, fetch 함수를 통해 api로 요청을 보내는데, fetch 함수에는 요청을 보낼 URL 링크를 적어야 합니다.
해당 링크는 apiRouter.js에 작성했었던 registerView controller를 작동시킬 URL인 "/api/videos/{해당 영상의 id}/view"를 작성해줘야 합니다. 그런데 handleEnded 함수에서 영상의 id를 알 수 없습니다.
따라서 템플릿을 렌더링 하는 pug에 프론트엔드에서 사용하는 자바스크립트가 해당 영상에 대한 정보를 알 수 있도록, 영상에 대한 정보를 남기는 코드를 작성해줘야 합니다.
watch.pug에 다음 코드를 추가합니다.
div(data-videoid=video._id)#videoContainer
위 코드는 영상을 보는 페이지인 watch.pug에 영상과 영상 컨트롤러들을 담는 div인 videoContainer에다가 html 기본 attribute인 data를 추가해서 html에 해당 영상의 id를 남기는 코드입니다.
주의할 점은 data를 저장할 때 data- 뒤에 오는 값들은 대문자, 소문자를 구분할 수 없다는 점입니다. 예를 들어 Video, vIdeo, viDeo, vidEo, videO가 전부 같은 video로 취급됩니다.
HTML attribute인 data에 대한 자세한 정보는 MDN 문서에서 확인하실 수 있습니다.
요소 검사를 해보면, videoContainer div에 영상의 id가 잘 저장된 것을 볼 수 있습니다.
이제 videoPlayer.js 파일로 돌아와서 함수를 마무리해봅시다.
// videoPlayer.js
const video = document.querySelector("video");
const videoContainer = document.getElementById("videoContainer");
const handleEnded = () => {
const { videoid } = videoContainer.dataset;
fetch(`/api/videos/${videoid}/view`, {
method: "POST",
});
};
video.addEventListener("ended", handleEnded);
videoContainer를 자바스크립트에서 선언하고, videoContainer.dataset 객체에 저장되어 있는 videoId를 불러와서 fetch 링크에 넣어줬습니다.
또, fetch의 method 값은 GET인데, apiRouer에 POST에 대한 contoller를 만들어 줬으니, fetch의 method 옵션에 "POST"를 작성했습니다.
view 함수가 작동되기 전의 영상 데이터는 다음과 같이 views가 0으로 저장되어 있습니다.
영상을 끝까지 재생하면 ended 이벤트가 감지되어 handleEnded 함수가 호출되고, handleEnded 함수의 작동이 끝나면 다음과 같이 해당 영상의 views가 1 증가된 것을 확인할 수 있습니다.
데이터 변경은 잘 되었지만 요소 검사 - 네트워크에 들어가 보면
view 요청이 마무리되지 않아서 계속 대기 중 상태로 멈춰있습니다.
사실 registerView controller에 사용했던 res.status()는 응답에 상태 코드를 추가하기만 할 뿐, 상태 코드를 보내지는 않습니다. 따라서, 그냥 status() 메서드가 아닌, 상태 코드를 보내고 연결을 끝낼 수 있는 응답 메서드인 sendStatus() 메서드를 사용해 상태 코드를 보내고 연결을 끝내봅시다.
registerView controller를 다음과 같이 수정합니다.
export const registerView = async (req, res) => {
const { id } = req.params;
const video = await VideoModel.findById(id);
if (!video) {
return res.sendStatus(404);
}
video.meta.views += 1;
await video.save();
return res.sendStatus(200);
};
코드를 저장한 뒤, 페이지를 새로고침하고 영상을 끝까지 다 보면
views가 정상적으로 증가되고 (제가 중간에 한번 테스트하느라 한번 더 동작돼서 3으로 증가된 것입니다.)
fetch 요청이 정상적으로 종료되었습니다.
이제 URL과 form을 사용하지 않고도 많은 기능들을 사용할 수 있으니 더 풍성한 페이지를 만들 수 있을 것 같습니다!
'토이 프로젝트 > 유튜브 클론코딩' 카테고리의 다른 글
[유튜브 클론코딩] Webpack (0) | 2023.08.06 |
---|---|
[유튜브 클론코딩] MongoDB 데이터베이스 모델 관계 연결 (0) | 2023.08.04 |
[유튜브 클론코딩] GitHub Login (0) | 2023.08.04 |
[유튜브 클론코딩] Sessions and Cookies (0) | 2023.08.01 |
[유튜브 클론코딩] mongoDB - CRUD (0) | 2023.07.26 |