유튜브 클론코딩 복습노트-2
-
이 포스팅은 노마드 코더의 "유튜브 클론코딩" 강의를 바탕으로 작성되었습니다. https://nomadcoders.co
-
썸네일 이미지 출처: https://developers.google.com/youtube
2021년 7월 22일
지난 포스팅에서 기초 셋업과 Node.js의 프레임워크인 Express의 기초에 대해 공부했습니다. 이어서 Express의 router 개념에 대해 알아보겠습니다.
지금까지 서버에서 받은 요청은 루트 /
하나 뿐입니다.
const handleHello = (req, res) => {
res.send("Hello!");
};
app.get("/", handleHello);
그런데도 코드가 차지하는 공간은 제법 있어 보입니다. 아마 컨트롤러의 덩치가 훨씬 커진다면 코드는 더욱 복잡해질 것입니다.
웹 서비스가 만들어짐에 따라 수십에서 수백 개의 경로가 생긴다면 코드는 훨씬 복잡해질 것입니다. 따라서 URL을 종류별로 묶어놓을 수 있는 장치가 필요합니다. 이 이야기는 밑으로 내려가면서 계속 이어가겠습니다.
도메인 설계
우선 사용자 입장에서 도메인을 설계해봅시다. 유튜브의 도메인은 크게 두 가지로 분리할 수 있습니다. video
와 user
입니다.
시청, 업로드, 삭제 등의 이벤트가 일어나는 video
와 로그인, 프로필 등을 관리하는 user
가 있습니다. 각각의 상황에 맞는 페이지를 만들어나가면 다음과 같을 것입니다.
video:
- /trending-videos
- /see-video
- /edit-video
- /search-video
- /upload-video
- /delete-video
user:
- /join
- /login
- /edit-user
- /remove-user
- /logout-user
- /see-user
이 경로들에 대한 요청을 모두 app.get()
으로 받고, 각각에 해당하는 컨트롤러를 만들고, 필요한 미들웨어까지 만든다면 server.js
파일은 필요 이상으로 복잡해집니다.
문제는 URL에도 있습니다. URL이 조직화되어있지 않기 때문에 /upload-video
, /delete-video
처럼 video
라는 문자열을 계속 반복해야 합니다.
router
Express의 router는 마치 상위 디렉터리 안에 하위 디렉터리들이 위치하는 것처럼 URL을 조직적으로 관리할 수 있게 해줍니다.
만약 /videos/delete
요청이 들어오면 해당 요청은 /videos
를 담당하는 라우터에게 보내집니다. 그리고 비디오 라우터가 /delete
를 담당하는 컨트롤러를 통해 요청에 응답하게 됩니다.
const globalRouter = express.Router();
const videoRouter = express.Router();
const userRouter = express.Router();
app.use("/", globalRouter);
app.use("/videos", videoRouter);
app.use("/users", userRouter);
이 프로젝트에서는 총 3개의 라우터를 사용합니다. 유저와 관련된 userRouter
, 동영상과 관련된 videoRouter
, 그리고 메인 페이지를 담당하는 globalRouter
입니다. 각각 express.Router()
로 라우터를 생성합니다. 그리고 app.use()
에 라우터가 담당하는 경로와 함께 라우터를 인자로 줍니다.
각각의 라우터가 담당하는 경로를 할당하는 방법은 이전과 똑같습니다.
const watch = (req, res) => {
res.send("Watch videos!");
};
videoRouter.get("/watch", watch);
지금까지 express app
에 get()
으로 요청을 받을 수 있게 해줬던 것처럼, 라우터에도 get()
을 통해 특정 경로의 요청을 받게 할 수 있습니다. videoRouter
에게 /watch
요청을 받을 수 있게 해줬습니다. 이제 사용자가 /videos/watch
를 요청하면 videoRouter
에서 처리할 것입니다.
여기서 볼 수 있듯이 각각의 라우터는 마치 하나의 '미니 어플리케이션'처럼 기능합니다. videoRouter
는 /watch
, /edit
등의 요청을 처리할 수 있는 작은 어플리케이션인 셈입니다.
파일 분할
import express from "express";
import morgan from "morgan";
const app = express();
const PORT = 4000;
const globalRouter = express.Router();
const videoRouter = express.Router();
const userRouter = express.Router();
const watch = (req, res) => {
res.send("Watch videos!");
};
videoRouter.get("/watch", watch);
app.use(morgan("dev"));
app.use("/", globalRouter);
app.use("/videos", videoRouter);
app.use("/users", userRouter);
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
import express from "express";
import morgan from "morgan";
const app = express();
const PORT = 4000;
const globalRouter = express.Router();
const videoRouter = express.Router();
const userRouter = express.Router();
const watch = (req, res) => {
res.send("Watch videos!");
};
videoRouter.get("/watch", watch);
app.use(morgan("dev"));
app.use("/", globalRouter);
app.use("/videos", videoRouter);
app.use("/users", userRouter);
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
Express 틀을 만들고, 라우터를 생성하고, 임시로 videoRouter
에게 /watch
컨트롤러를 달아주었습니다. 라우터를 만들어 경로별 요청을 분류한 것까지는 좋았는데, server.js
파일이 복잡해졌습니다. 도메인 설계에서 구상한 모든 페이지를 만든다면 라우터와 컨트롤러 등이 섞여 보기에 좋지 않을 것입니다.
이제 라우터는 라우터끼리, 컨트롤러는 컨트롤러끼리 모여있을 수 있도록 프로젝트 디렉토리를 재구성해보겠습니다.
-
src
폴더 안에routers
와controllers
폴더를 생성합니다. -
routers
폴더 안에globalRouter.js
,videoRouter.js
,userRouter.js
파일을 만듭니다. -
controllers
폴더 안에videoController.js
,userController.js
파일을 만듭니다.controllers
폴더 안에globalController.js
를 만들지 않는 이유는 홈 화면에 표시될 컨텐츠의 종류도 결국 유저 아니면 비디오와 관련된 것이기 때문입니다.
이제 server.js
에 적었던 라우터 관련 코드를 각각의 파일로 옮기겠습니다. globalRouter
를 예시로 설명하겠습니다.
import express from "express";
const globalRouter = express.Router();
export default globalRouter;
라우터도 Express의 기능이므로 express를 불러옵니다. 그리고 globalRouter
를 생성한 뒤 export
하여 server.js
에서 불러올 수 있도록 합니다.
videoRouter
, userRouter
에도 같은 작업을 반복합니다.
그리고 server.js
에서 중복되는 코드를 지우고 라우터들을 모두 불러옵니다.
import express from "express";
import morgan from "morgan";
import globalRouter from "./routers/globalRouter";
import videoRouter from "./routers/videoRouter";
import userRouter from "./routers/userRouter";
const app = express();
const PORT = 4000;
app.use(morgan("dev"));
app.use("/", globalRouter);
app.use("/videos", videoRouter);
app.use("/users", userRouter);
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
파일이 훨씬 깔끔해졌습니다.
라우터 설계
이제 파일 정리를 마치고 각각의 라우터 구조를 설계해보겠습니다. 현재 시점에서는 대략적인 방향만 잡아놓고 앞으로 수정해나갈 계획입니다.
globalRouter
globalRouter
는 메인 화면과 관련된 페이지들을 모아놓은 라우터입니다. 다음과 같이 구성할 수 있을 것입니다.
globalRouter.get("/", trending);
globalRouter.get("/join", join);
globalRouter.get("/login", login);
globalRouter.get("/search", search);
사용자가 메인 페이지에 들어오면 유튜브는 인기동영상 리스트를 보여줍니다. 이는 trending
컨트롤러가 담당합니다. 그리고 회원가입, 로그인, 검색 등의 기능이 메인 페이지에 들어갈 수 있습니다.
videoRouter
videoRouter.get("/upload", upload);
videoRouter.get("/:id", see);
videoRouter.get("/:id/edit", edit);
videoRouter.get("/:id/delete", deleteVideo);
비디오와 관련해서는 업로드, 시청, 수정, 삭제 등의 기능이 있을 것입니다.
여기서 /:id
라고 적혀 있는 부분은 URL 파라미터입니다. 유튜브의 모든 동영상은 동영상을 시청하고 댓글을 남길 수 있는 페이지를 가지고 있지만, 그렇다고 해서 구글이 모든 동영상을 위한 페이지를 직접 제작해주는 것은 아닙니다.
/:id
에는 해당 동영상의 id값이 들어가게 되고, 미리 만들어진 탬플릿 페이지에 해당 동영상과 관련된 모든 데이터가 채워집니다. 이 부분은 데이터베이스 파트에서 자세히 다룰 예정입니다.
userRouter
userRouter.get("/logout", logout);
userRouter.get("/edit", edit);
userRouter.get("/remove", remove);
userRouter.get("/:id", see);
유저 관련 기능에는 로그아웃, 유저 프로필 수정, 회원탈퇴, 그리고 프로필 보기가 있습니다. video와 마찬가지로 유저 정보를 데이터베이스에서 불러올 때 더 자세히 다루겠습니다.
controller 연결
각각의 라우터를 설계할 때 URL과 함께 해당 요청을 처리하는 컨트롤러 함수를 함께 전달했습니다. 이제 임시로 컨트롤러를 만들어보겠습니다.
userController
export const join = (req, res) => res.send("Join");
export const login = (req, res) => res.send("Login");
export const edit = (req, res) => res.send("Edit user");
export const remove = (req, res) => res.send("Remove user");
export const logout = (req, res) => res.send("Logout user");
export const see = (req, res) => res.send("See user");
모든 컨트롤러는 라우터에서 쓸 수 있게끔 export
해줍니다. 여기서 join
과 login
은 globalRouter
가, 나머지 컨트롤러는 userRouter
가 사용합니다.
videoController
export const trending = (req, res) => res.send("trending");
export const see = (req, res) => res.send("watch");
export const edit = (req, res) => res.send("edit");
export const search = (req, res) => res.send("Search");
export const upload = (req, res) => res.send("upload");
export const deleteVideo = (req, res) => res.send("deleteVideo");
역시 모든 컨트롤러를 export
했습니다. trending
은 globalRouter
가 가져다 쓸 것입니다. 나머지 컨트롤러는 videoRouter
가 사용합니다.
라우터에서 import 하기
이제 controllers
폴더에 만들어놓은 컨트롤러들을 각각의 라우터가 import
해서 사용해야 합니다.
import express from "express";
import { trending, search } from "../controllers/videoContoller";
import { join, login } from "../controllers/userContoller";
const globalRouter = express.Router();
globalRouter.get("/", trending);
globalRouter.get("/join", join);
globalRouter.get("/login", login);
globalRouter.get("/search", search);
export default globalRouter;
globalRouter
에서 사용하는 컨트롤러를 다음과 같이 import
합니다. 다른 컨트롤러도 마찬가지입니다.
import express from "express";
import { logout, edit, remove, see } from "../controllers/userContoller";
const userRouter = express.Router();
userRouter.get("/logout", logout);
userRouter.get("/edit", edit);
userRouter.get("/remove", remove);
userRouter.get("/:id", see);
export default userRouter;
import express from "express";
import { upload, see, edit, deleteVideo } from "../controllers/videoContoller";
const videoRouter = express.Router();
videoRouter.get("/upload", upload);
videoRouter.get("/:id", see);
videoRouter.get("/:id/edit", edit);
videoRouter.get("/:id/delete", deleteVideo);
export default videoRouter;
이제 이번 프로젝트의 라우터 설계의 윤곽이 어느정도 잡혔습니다. 앞으로 데이터베이스를 연동하고, HTML을 렌더링하는 과정을 거치면서 이 부분은 계속 수정될 것입니다.
다음 포스팅에서는 pug를 사용해 서버에서 HTML을 효율적으로 렌더링하는 방법에 대해 알아보겠습니다.
<참고 문서>
Author And Source
이 문제에 관하여(유튜브 클론코딩 복습노트-2), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@soonitoon/유튜브-클론코딩-복습노트-2저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)