909 Devlog

[유튜브 클론코딩] express 서버 라우팅 본문

토이 프로젝트/유튜브 클론코딩

[유튜브 클론코딩] express 서버 라우팅

구공구 2023. 7. 17. 19:14
728x90

1. Router

router는 앞에서 보았던 컨트롤러URL의 관리를 쉽게 해주는 개발자를 위한 구조입니다.

 

router를 만들기 전에 간단한 기획을 생각해 두고 시작합니다.

프로젝트에 대해 생각할 때 가장 먼저 생각해야 하는 건 데이터입니다.

예를 들어 유튜브 클론코딩에서는 영상 데이터, 유저 데이터를 사용합니다.

    // global router
/ -> 홈페이지
/join -> 회원가입
/login -> 로그인
/search -> 영상 검색

   // user router
/users/edit -> 유저 정보 수정
/users/delete -> 유저 삭제

   // video router
/videos/watch -> 영상 시청
/videos/edit -> 영상 수정
/videos/delete -> 영상 삭제
/videos/comments -> 영상 댓글
/videos/comments/delete -> 영상 댓글 삭제

1.1 Router 생성

위에서 기획했던 구조로 직접 Router를 만들어 봅시다

const globalRouter = express.Router();
const userRouter = express.Router();
const videoRouter = express.Router();

위에서 기획했던 대로, 글로벌, 유저, 비디오 라우터를 따로 생성했습니다.

  • 글로벌 라우터는 / 로 시작하고
  • 유저 라우터는 /users로 시작하며
  • 비디오 라우터는 /videos로 시작하기 때문에 

use()를 사용해 URL에 따라 사용할 라우터를 지정해 주겠습니다.

const globalRouter = express.Router();
const userRouter = express.Router();
const videoRouter = express.Router();

app.use("/", globalRouter);
app.use("/users", userRouter);
app.use("/videos", videoRouter);

1.2 Router로 get 요청받기

URL의 처음 부분을 받고, 어느 라우터를 사용할지 지정했으니 다음으로는 URL의 뒷부분에 따라 get요청을 response 해 줄 함수를 작성해 보겠습니다.

const globalRouter = express.Router();
globalRouter.get("/", (req, res) => {
    return res.send("Home");
});

const userRouter = express.Router();
userRouter.get("/edit", (req, res) => {
    return res.send("Edit User");
});

const videoRouter = express.Router();
videoRouter.get("/watch", (req, res) => {
    return res.send("Watch Video")
});

app.use("/", globalRouter);
app.use("/users", userRouter);
app.use("/videos", videoRouter);

이제 사용자가 접속한 주소에 따라 사용할 라우터가 결정되며, 그 라우터에서도 주소에 따라 사용할 함수가 결정되어 response가 보내지게 됩니다.

 

  • localhost:4000에 접속하면 화면에 Home이라는 글자가 뜨고
  • localhost:4000/users/edit에 접속하면 Edit User이 뜨며
  • localhost:4000/videos/watch에 접속하면 Watch Video가 뜨게 됩니다.

2. export

한 파일에 모든 주소에 대응하는 코드를 다 작성하게 되면 코드가 너무 길어지고 나중에 추가로 작업하기 힘들어지니 라우터에 따라 파일을 나누어서 코드를 정리해 보겠습니다.

routers 폴더 생성

routers 폴더를 생성하여 내부에 Router 파일들을 생성했고

위와 같은 구조로 폴더와 파일을 생성했습니다.

 

또, 새로 생성한 Router 파일에 기존 server.js에 생성했던 router와 그 함수들을 옮겼습니다.

*각 파일에 express를 import 해줘야 합니다.*

// globalRouter.js

import express from "express";

const globalRouter = express.Router();
globalRouter.get("/", (req, res) => {
    return res.send("Home");
});
// userRouter.js

import express from "express";

const userRouter = express.Router();
userRouter.get("/edit", (req, res) => {
    return res.send("Edit User");
});
// videoRouter.js

import express from "express";

const videoRouter = express.Router();
videoRouter.get("/watch", (req, res) => {
    return res.send("Watch Video")
});

 

코드를 다 나누어서 저장했기 때문에 이제 server.js 파일은 훨씬 깔끔해져 보기 편해졌습니다.

하지만 server.js에서 globalRouter, userRouter, videoRouter들이 정의되지 않았다는 오류가 뜨게 되었습니다.

 

이 문제를 해결하기 위해 export를 알아봅시다

2.1 export defalut

예시를 한번 보고 자세하게 알아봅시다.

// globalRouter.js

import express from "express";

const globalRouter = express.Router();
globalRouter.get("/", (req, res) => {
    return res.send("Home");
});

export default globalRouter; <-- 추가
// server.js

import express from "express";
import morgan from "morgan";
import globalRouter from "./routers/globalRouter"; <-- 추가

const PORT = 4000;

const app = express();

...

 

각각의 파일은 모두 따로 분리되어 있기 때문에 a파일에서 만든 변수를 b파일에서 사용할 수 없습니다.

 

하지만 코드를 더 깔끔하게 만들기 위해 파일을 분리해서 저장했었고, 위 문제를 해결하기 위해 globalRouter.js 에서 만든 globalRouter라는 변수를 export(수출)했고, server.js에서 globalRouter를 import(수입)했습니다.


export에 대해 더 알아보기 전에, 코드를 한번 더 정리하고 알아봅시다.

 

이때까지 했던 방법으로 코딩을 하다 보면 Router.js 파일들도 정리하기 전의 server.js처럼 URL마다 대응하는 controller 함수가 계속 작성되어 코드가 매우 길어지고 코드 수정이 어렵게 될 것입니다.

 

귀찮을 수도 있지만, 라우터와 컨트롤러를 섞어서 쓰는 것은 좋지 않은 방법입니다.

컨트롤러는 그냥 함수일 뿐이고, 라우터가 그 함수를 이용하는 입장입니다.

위에서 생성했던 파일은 Router&Controller.js가 아닌 Router.js 이므로 따로 만들어서 정리하는 게 좋은 방법입니다.

 

따라서, Router.js 내부에서 사용되는 controller 함수들을 또 다른 파일로 만들어서 정리해 봅시다.

controllers 폴더 생성

routers 폴더를 추가했던 것처럼, controllers 폴더를 만들고 userController.js와 videoController.js를 만들었습니다.

globalController가 없는 이유는 router는 그저 URL을 깔끔하게 하기 위해 쓰는 것일 뿐이며, 만들 사이트의 핵심인 유저와 영상에 관련된 함수는 userController와 videoController에 작성될 것이기 때문에 globalController가 필요 없습니다.


2.2 export

이제 router.get() 함수에서 사용했던 익명 함수들을 이름을 붙여 controller.js 파일들에 작성해 봅시다.

// userController.js

const join = (req, res) => res.send("Join");
// videoController.js

const trending = (req, res) => res.send("Home Page Videos");
const watch = (req, res) => res.send("Watch");
const edit = (req, res) => res.send("Edit");

userController.js에 가입에 대응하는 함수를 만들었고,

videoController.js에 추천 영상, 영상 시청, 영상 수정에 대응하는 함수를 만들었습니다.

 

Router에서 했던 것처럼 변수를 export 해봅시다.

 

userController에서는 

export default를 통해서 join을 다른 파일로 보낼 수 있지만,

videoController에서는 

export default를 통해 3가지의 함수를 다른 파일로 보낼 수 없습니다.

 

export default는 한 파일에서 단 하나의 변수만 export 할 수 있고,

export는 한 파일에서 여러 개의 변수를 export 할 수 있습니다.

 

export default를 사용하는 이유는 import 하는 파일에서 변수 이름을 새로 만들 수 있기 때문입니다.

export default를 검색하면 export default 사용을 피해야 하는 이유에 대해 많이 올라와 있으니, 한번 보시고 선택하셔서 사용하면 좋을 것 같습니다.

 

따라서 export를 사용해 여러 개의 변수를 export 해보겠습니다.

 

// userController.js

export const join = (req, res) => res.send("Join");
export const edit = (req, res) => res.send("Edit User");
export const deleteUser = (req, res) => res.send("Delete User");
// videoController.js

export const trending = (req, res) => res.send("Home Page Videos");
export const watch = (req, res) => res.send("Watch");
export const edit = (req, res) => res.send("Edit");

 

이제 import 할 차례입니다.

// globalRouter.js

import express from "express";
import { join } from "../controllers/userController"; 
import { trending} from "../controllers/videoController";

const globalRouter = express.Router();
globalRouter.get("/", trending);
globalRouter.get('/join', join);

export default globalRouter;
// userRouter.js

import express from "express";
import { edit, remove } from "../controllers/userController";

const userRouter = express.Router();
userRouter.get("/edit", edit);
userRouter.get("/delete", remove);

export default userRouter;
// videoRouter.js

import express from "express";
import { watch, edit } from "../controllers/videoController";

const videoRouter = express.Router();
videoRouter.get("/watch", watch);
videoRouter.get("/edit", edit);

export default videoRouter;

exprort default를 사용했던 것과 다른 점은

export 하는 파일에서 변수마다 앞에 export를 붙여주었고

import 하는 파일에서는 {중괄호}를 붙여서 import 했으며, Router.get() 안에 익명함수들을 불러온 함수들로 교체했습니다.

중괄호를 사용하여 import 하는 이유는 한 파일에서 여러 변수들을 받기 위함입니다.

3. URL Parameters

parameter는 URL안에 변수를 포함시킬 수 있게 해 줍니다.

 

지금 이 글의 주소를 보면

gugonggu.tistory.com/(숫자)로 되어 있는 게 보일 겁니다

이 (숫자)가 변수입니다. 이 숫자는 제 블로그의 글마다 다른 숫자로 바뀝니다.

 

parameter가 없었다면

gugonggu.tistory.com/1

gugonggu.tistory.com/2

gugonggu.tistory.com/3

... 을 모두 직접 코딩해야 하고 유저는 수시로 글을 포스팅하기 때문에 개발자가 이 모든 경우에 대응하는 것은 불가능합니다.

 

그렇기 때문에 parameter가 존재하며, 사용합니다.

 

이제 parameter를 사용해 봅시다.

// videoRouter.js

import express from "express";
import { watch, edit } from "../controllers/videoController";

const videoRouter = express.Router();
videoRouter.get("/edit", edit);
videoRouter.get("/:id", watch); /*<--- parameter 사용*/
// 주의 - 위의 두 가지 get() 함수의 순서를 바꾸었습니다.

export default videoRouter;

parameter는 : 뒤에 변수명을 써서 사용합니다. 변수명은 다른 이름으로도 사용 가능합니다.

 

parameter를 controller에서도 사용할 수 있습니다.

// videoController.js

export const trending = (req, res) => res.send("Home Page Videos");
export const watch = {
    console.log(req.params);
    return res.send("Watch");
};
export const edit = (req, res) => res.send("Edit");

아래 주소에 들어가면

URL

코딩해 놓은 대로 Watch란 글자가 뜨고

http://localhost:4000/videos/456135465 내용

마찬가지로 코딩해 놓은 대로 터미널에 req.params가 뜹니다.

req.params와 log

req.params는 객체이니, req.params.변수명을 사용하면 456135465를 직접 console.log() 할 수도 있습니다.

 

3.1 parameter 사용 순서

위의 videoRouter.js에서 아래 순서로 get()을 사용했습니다.

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

이 순서를 아래와 같이 바꾸고, localhost:4000/videos/edit에 접속하려 하면 어떻게 될까요?

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

제 의도는 Edit이라는 글자가 떠 있는 edit 페이지를 보기 위해 링크를 쳐서 접속했는데

화면에 Watch라는 글자가 떠 있고 

터미널에 req.params가 뜨게 되었습니다.

 

이는 express가 watch함수를 사용하는 videoRouter.get()을 먼저 보고, URL에 있는 edit을 변수로 봤기 때문에 edit 함수가 아닌 watch 함수를 사용해서 뜬 결과입니다.

 

이 문제를 해결하기 위해 순서를 바꾸고, 정규식도 사용해 봅시다.

3.2 정규식

정규식은 문자열로부터 특정 정보를 추출해 내는 방법입니다.

정규식은 정규식 블로그에서 자세하게 알아볼 수 있습니다.

 

정규식에 대해 간단하게 알아보셨다면,

videoRouter.get("/:id(\\d+)", watch);

위 정규식이 숫자만 받는다는 것을 유추하실 수 있을 것입니다.

 

위에서 접속했던 localhost:4000/videos/edit에 다시 접속해 보면,

위와 같은 결과가 뜨면서 이제 /videos/(숫자)만 인식하게 되었습니다.

728x90