서버 구현하기, router와 controller

be kid·2022년 2월 3일
0

BEB - 웹 개발 과정

목록 보기
22/28

오늘은 API 문서와 기타 요구사항들을 바탕으로 서버를 구현하는 실습을 했다.

진행한 실습을 바탕으로 알게 된 내용들을 정리해두려고 한다.

구조

우선 핵심 폴더와 파일의 구조는 다음과 같다.

  • 서비스에 필요한 미들웨어와 웹 서버를 실행하는 코드가 있는 app.js
  • API 요청을 수행하는 라우터가 작성되어 있는 파일들이 담긴 router 폴더
  • API 요청을 수행하는 코드가 있는 파일들이 담긴 controller 폴더

실습한 내용을 그대로 정리하지는 않고 임의의 정보들이 있고 이 정보들에 대해 GET, POST, PUT, DELETE 요청을 처리하는 어떤 서버를 가정하고 정리해보려한다.

app.js

const express = require("express");
const cors = requires("cors");
const app = express();
const port = 3001;

const router = require("./router/awesomeRouter");

app.use(cors());
app.use(express.json());

app.use("/awesome", router);

app.get("/", (req, res) => {
  res.status(200).send("Welcome, Awesome server");
});

app.use((req, res, next) => {
  res.status(404).send("Not Found!");
});

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send({
    message: "Internal Server Error",
    stacktrace: err.toString()
  });
});

app.listen(port, () => {
  console.log(`[RUN] Awesome Server... | http://localhost:${port}`);
});

module.exports = app;

지난번에 했던 실습과 다른 점은 우선 router를 다른 곳에서 가지고 오고 있다는 점.
그리고 app.use(express.json());을 통해 body-parser를 대신해주고 있다.
express에 내장된 미들웨어 기능이다.
여기까지의 내용으로는 /awesome 이라는 URL에서 어떠한 요청이 들어왔을 때 router라는 이름의 awesomeRouter를 실행하게 될 것이라는 사실을 알 수 있다.
그럼 이제 awesomeRouter를 볼 차례다.

awesomeRouter.js

const {getSomething, postSomething, putSomething, deleteSomething} = require("../controller/awesomeController");
const express = require("express");
const router = express.Router();

router.get("/", getSomething);
router.get("/:id", getSomethingById);
router.post("/", postSomething);
router.put("/", putSomething);
router.delete("/", deleteSomething);

module.exports = router;

라우터 파일 안에는 대단한건 없어보인다.
컨트롤러 파일로부터 무언가 잔뜩 가져왔는데, http 메소드에 따라 작업을 해줄 무언가로 보인다.
const router = express.Router();와 같이 선언하면 router를 exports해줄 수 있게 되고 결과적으로 더 위에 적은 app.js 에서 가져다 사용할 수 있게 되었다.
각각의 get, post, put, delete 요청에 맞는 Something들을 가져다 붙여준 모습이다.
url이 / 라고만 되어있지만 app.js에서 app.use("/awesome", router); 처럼 적어뒀기 때문에 /awesome/ 이라고 볼 수 있다.
/:id와 같은 경우도 있는데 이것은 컨트롤러에서 같이 보는게 좋을 것 같아 일단 넘긴다.
사실 이 파일 내에서 Something들도 모두 구현해도 되지만 그랬다가는 파일이 너무 지저분해질 것이다. 이 파일은 순수하게 라우팅 기능만 해주고 실제로 요청에 대한 작업을 수행해주는 controller들을 따로 분리시킨 것이 controller 파일이 되는 것이다.
드디어 awesomeController를 볼 차례다.

awesomeController.js

module.exports = {
  getSomething: (req, res) => {
    // 무언가 get을 하는 코드
    return res.status(200).json(something);    
  },
  getSomethingById: (req, res) => {
    // 무언가 get을 하는 코드
    return res.status(200).json(something);    
  },
  postSomething: (req, res) => {
    // 무언가 post를 하는 코드
    return res.status(201).json({
      message: "POST success!",
    });
  },
  putSomething: (req, res) => {
    // 무언가 put을 하는 코드
    return res.status(201).json(something);
  },
  deleteSomething: (req, res) => {
    // 무언가 delete를 하는 코드
    return res.status(200).json(something);
  }

아주 임시로 작성한 코드이다. 사실 위와 같은 구조는 처음보긴 하는데 이해하는데 크게 어려움은 없었기 때문에 문제는 없었다. 함수를 작성하고 exports 하는 것이 더 익숙한 방법이긴하다.
또 비슷하게 require말고 import에 익숙하긴한데, 이는 찾아보니 exports, import가 ES6에서 추가된 내용이고 사용하려면 babel을 거쳐야한다는 것 같다. 다만 node.js 버전 13.2부터는 정식으로 지원이 된다고 하니 상황 잘 봐서 쓰면 될 것 같기는 하다. (좀 더 알아볼 필요가 있다.)
리턴에서 보이는 res.json()은 응답으로 보내줄 데이터를 parse할 수 있는 형태로, 즉 json 문자열로 변환해서 보내준다. -> JSON.stringify()
객체, 배열, 문자열, 부울, 숫자 또는 null을 포함한 모든 유형이 가능하다.

처음붙터 다시 쭉 정리해보면, 지금은 라우터나 컨트롤러가 한개씩 뿐이지만 더 프로젝트가 커진다면 다음과 같은 구조가 될 것이다.

app.js - router1 - controller1
				 - controller2
	   - router2 - controller3
       			 - controller4
       			 - controller5
       - router3 - controller6
       ...

프론트와 백에서의 router 설계와 API 문서 작성 등 여러모로 서로 맞춰야할 것들이 많은 것 같다.


추가로 오늘 실습을 진행하며 추가로 알게 된 것

일단 request 관련
어떤 요청이 들어왔을 때 (GET), req.query 에 url 쿼리문이 잘 정리되어 담겨있다.
필요한 것들을 구조분해할당해서 가져다 쓰면 된다.
POST의 경우에는 JSON 형식으로 데이터를 보내오게 될 텐데 이는 req.body에 잘 담겨있다.
마찬가지로 예쁘게 가져다 쓰면 된다.
쿼리문이 아닌 특정 파라미터로 요청이 들어오는 경우에는 req.params에서 가져다 쓸 수 있다. (위에서 일단 넘겼던 /:id 와 같은 경우 id가 변수처럼 사용되어 req.params.id 로 접근할 수 있다)
https://expressjs.com/ko/api.html
문서를 열심히 읽어야겠다. 영어인것이 또 나를 힘들게 하지만..

또 한가지는 Nullish coalescing operator (널 병합 연산자)
널 병합 연산자(??)는 왼쪽 피연산자가 null 또는 undefined일 때 오른쪽 피연산자를 반환하고, 그렇지 않으면 왼쪽 피연산자를 반환하는 논리 연산자이다.

이걸 알기 전에는 삼항 조건 연산자로 req로 받은 특정 데이터가 있는지 없는지 확인하고 없는 경우에는 다른 데이터를 저장하는 식의 코드를 작성했었는데 여기서는 널 병합 연산자를 사용하니 코드가 훨씬 간결해졌다. 예를 들면 다음과 같이 되는 것이다.

const { id } = req.query;
const defaultId = "be-kid";
// 삼항 조건 연산자를 사용한 경우
const findId = id === undefined ? defaultId : id;
// 널 병합 연산자를 사용한 경우
const findId = id ?? defaultId;

위에서 적은대로 왼쪽 id가 null이나 undefined면 오른쪽 defaultId가 저장되는 식이다.
널 병합 연산자말고 논리 연산자 OR (||)를 사용할 수 있는데 이는 null과 undefined가 아닌 falsy한 값을 모두 포함해서 예기치 않는 결과(0, '' 등을 유효한 값으로 생각하는 경우)가 나올 수 있어 상황에 맞게 사용하는 것이 좋겠다.

profile
개쩌는 개발자가 되고 싶다 !

0개의 댓글