Node JS #4

Router

Express에서의 Router는 컨트롤러와 URL의 관리를 쉽게하는 것이다.

Routering : URL이 어떻게 시작하는지에 따라 나누는 방법

아래와 같은 URL을 설계하였다고 하자.

# url

글로벌 라우터

/ -> Home

/join -> Join

/login -> Login

/search -> Search

유저 라우터

/users/:id -> See User

/users/logout -> Log Out

/users/edit -> Edit MY Profile

/users/delete -> Delete MY Profile

비디오 라우터

/videos/:id -> See Video

/videos/:id/edit -> Edit Video

/videos/:id/delete -> Delete Video

/videos/upload -> Upload Video

만일 라우터가 없다면

app.get("/",handleHome");
app.get("/join",handleJoin");
...
app.get("/videos/upload",handleUpload);

와 같이 수 많은 URL들을 직접 등록해야 할 것이고, 관리 또한 어려울 것이다.

따라서 라우터를 사용한다.

app.use("/PATH", Router);
// /PATH로 시작하는 URL이 요청으로 들어오면, Express는 Router로 들어간다.

예제 코드를 통해 알아보자.

//app.js

import express from "express";
import morgan from "morgan";
const PORT = 4000;
const app = express();

const logger = morgan("dev"); // midderware 등록
app.use(logger);

// 글로벌 라우터, 핸들러 등록
const globalRouter = express.Router();
const handleHome = (req, res) => res.send("Home");
globalRouter.get("/", handleHome);

// 유저 라우터, 핸들러 등록
const userRouter = express.Router();
const handleEditUser = (req, res) => res.send("Edit User");
userRouter.get("/edit", handleEditUser);

// 비디오 라우터, 핸들러 등록
const videoRouter = express.Router();
const handleWatchVideo = (req, res) =>
  res.send("Watch Video");
videoRouter.get("/watch", handleWatchVideo);

// 라우터를 app에 등록
app.use("/", globalRouter);
app.use("/videos", videoRouter);
app.use("/users", userRouter);

// 서버 구동
app.listen(PORT, () =>
  console.log("Server listening on port 4000")
);

위 경우 구조를 나타내면 다음 그림과 같다.

image

확실히 URL을 체계적으로 관리할 수 있어 편하다는 장점을 느낄 수 있다.


Router와 Handler 분리해서 관리하기

위에서는 Router와 Handler를 하나의 파일에 모두 집어넣어서 URL이 많아지게 되면 관리하기 불편하다.

따라서 Router와 Handler를 분리하자.

코드를 작성할때는 일단 돌아가게 만든뒤,

코드를 정리하자. - Clean Code

파일 구조를 다음과 같이 개선하자. (brew install tree 설치 후 tree . 입력하면 볼 수 있다.)

image

controller.js에서는 각 컨트롤러를 정의한다.

//userController.js
export const join = (req, res) => res.send("Join");
export const edit = (req, res) => res.send("Edit User");
export const remove = (req, res) => res.send("Remove User");
export const login = (req, res) => res.send("Login");
export const logout = (req, res) => res.send("Log out");
export const see = (req, res) => res.send("See User");

router.js에서는 그 정의된 컨트롤러를 라우터에 지정한다.

//userRouter.js
import express from "express";
import {
  edit,
  remove,
  login,
  logout,
  see,
  join,
} from "../controllers/userController";
const userRouter = express.Router();

userRouter.get("/logout", logout);
userRouter.get("/login", login);
userRouter.get("/edit", edit);
userRouter.get("/remove", remove);
userRouter.get("/join", join);
userRouter.get(":id", see);

export default userRouter;

server.js에서는 라우터들을 등록한다.

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

참고로 글로벌 라우터는 URL을 깔끔하게 만들기 위해 예외적인 라우터라고 받아드리면 된다.


export default vs export

앞서 보았듯, 파일들을 분할하여 관리하면 굉장히 편한데, 이는 Node.js의 특징 때문이다.

Node.js는 모든 파일은 모듈이고, 모두 독립되어 있다.

기본적으로 파일은 private 상태이기 때문에, 다른 파일에서 사용하려면 export를 해주고, import 시켜야 한다.

export에는 두가지 방법이 있다.

  1. default export - 한 파일에 하나만 export -> 이름 달라도 됨
  2. export - 한 파일에 여러개 export -> 이름 같아야함, import에 {} 필요

예제 코드를 다시 보면서 알아보자.

//userController.js
//export default가 아닌 export이기에 여러 개를 export할 수 있지만 import시 중괄호를 써야하고, 이름이 같아야한다.
export const join = (req, res) => res.send("Join");
export const edit = (req, res) => res.send("Edit User");
export const remove = (req, res) => res.send("Remove User");
export const login = (req, res) => res.send("Login");
export const logout = (req, res) => res.send("Log out");
export const see = (req, res) => res.send("See User");
//userController.js
import express from "express"; // 모듈을 node_modules에서 import
import {
  edit,
  remove,
  logout,
  see,
} from "../controllers/userController"; // 특정 PATH에 있는 모듈을 가져온다. 이때 중괄호를 쓰고, 이름이 같은 것을 확인
const userRouter = express.Router();

userRouter.get("/logout", logout);
userRouter.get("/edit", edit);
userRouter.get("/remove", remove);
userRouter.get(":id", see);

export default userRouter; //export default이기에 하나만 export가능, import시 이름이 달라도 된다.
//server.js
import express from "express";
import morgan from "morgan";
import userRouter from "./routers/userRouter"; // 이때 굳이 userRouter가 아니라 xxxxx라고 해도 된다.
const PORT = 4000;

const app = express();

const logger = morgan("dev");
app.use(logger);

app.use("/users", userRouter);

const handleListening = () =>
  console.log(
    `Server listening on port http://localhost:${PORT}`
  );

app.listen(4000, handleListening);

Parameter in URL

URL 설계본을 다시 살펴보자.

유저 라우터

/users/:id -> See User

/users/logout -> Log Out

/users/edit -> Edit MY Profile

/users/delete -> Delete MY Profile

여기서 /users/:id에서 :가 나타내는 것은 parameter를 나타낸다.

만일 parameter가 없다면 우리는

const userRouter=express.Router();
userRouter.get("/1",see);
userRouter.get("/2",see);
userRouter.get("/3",see);
userRouter.get("/4",see);
...
userRouter.get("/99999",see);

이렇게 수많은 ID들의 URL을 직접 설정해주어야 할것이고 매우 귀찮은 작업이 될 것이다.

: 표시는 express에게 URL에 포함된 정보가 이게 변수라는 것을 알게 해준다.

그리고 request object는 이 정보를 가지게 된다.

예시 코드

import express from "express";
import morgan from "morgan";
const PORT = 4000;
const app = express();

const logger = morgan("dev"); // midderware 등록
app.use(logger);

// 유저 라우터, 핸들러 등록
const userRouter = express.Router();

userRouter.get("/:id", (req, res) => {
  console.log(req); // request 객체 콘솔에 출력
  res.send(`req.params.id :${req.params.id}`);
});

// 라우터를 app에 등록
app.use("/users", userRouter);

// 서버 구동
app.listen(PORT, () =>
  console.log("Server listening on port 4000")
);

localhost:4000/user/13에 접속하면

콘솔에 request 객체 내용중에 params: { id: ‘13’ }, 이 부분이 나타난다.

image

그리고 페이지에도 해당 id 값이 잘 나타내지는 것을 확인할 수 있다.


앞서 본 URL은 잘 작동한다.

그런데, 만일 localhost:4000/user/abcdef에 접근한다면?

image

다음과 같이 진짜 무식하게 id값에 abcdef라는 값을 집어넣게 된다.

문제점 1 - id 값에는 숫자만 올 수 있는데, 아무런 필터 없이 문자도 집어넣게 된다.

문제점 2 - 만일 라우팅을 2개 이상 해줄 경우, parameter가 있는 URL을 위에 써주면 다른 부분은 도달조차 하지 못한다.

// 유저 라우터, 핸들러 등록
const userRouter = express.Router();

// 어떠한 문자가 와도 전부 id로 출력하기 때문에 다 여기서 걸러진다.
userRouter.get("/:id", (req, res) => {
  console.log(req);
  res.send(`req.params.id :${req.params.id}`);
});

// 주소창에 greet이라 써도 위에서 id가 greet인 경로로 해석되기 때문에 이 부분은 절대 도달 불가능하다.
userRouter.get("greet", () => {
  res.send("Hello!"); // 절대 실행되지 않음
});

image

따라서 id값에는 숫자만 올 수 있게 하는 장치가 필요하다.

Regular Expression in URL

videoRouter.get("/:id(\\d+)", see); 숫자로  URL만 id값으로 설정

숫자가 아닌 문자로 접근했을때 에러 발생하는 모습

image

정규 표현식을 이용해 상술되었던 문제점 2개를 모두 해결할 수 있다.

Categories:

Updated:

Leave a comment