포스트

2024.05.02 프로그래머스-express 라우팅 분리해보기

새로운 프로젝트 만들기

  • 팬카페 컨셉의 사이트
  • 회원은 여러명의 아티스트의 팬이 될 수 있다.
  • https://github.com/ykdman/fantom

기능

회원

  • 로그인
  • 회원 정보 조회
  • 회원 가입
  • 회원 탈퇴

팬 되기

  • 회원이 팬을 맺은 아티스트를 위한 팬 개인의 기록소 만들기
  • 기록소 수정
  • 기록소 삭제

API 설계

회원

  • 로그인 POST‘/login’
    • req : body (id, pw)
    • res : ‘로그인 성공’
    • redirect : main page
  • 회원 가입 POST‘/signup’
    • req : body (id, pwd, name)
    • res : 회원가입을 환영합니다.
    • redirect : ?
  • 회원 정보 조회 GET‘/users/:id’
    • req : params (id)
    • res : json (id, pwd?, name)
    • redirect :
  • 회원 탈퇴 DELETE‘/users/:id’
    • req : params (id)
    • res : message 탈퇴가 완료되었습니다.

##

Architecture

  • 소스 구성
  • src
    • model : 데이터 저장소
    • controllers : 핸들러 함수 저장소
    • index.js : 서버 코드

회원가입 api 작성

  • method : POST
  • user.controller.js
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
31
32
33
34
35
36
/** user.controller.js*/
const db = require("../model/data.js");

/**
 * user 회원 가입 API : POST
 * @param {Request} req
 * @param {Response} res
 * @returns {Response}
 */
const signupUser = (req, res) => {
  const { id, pwd, name } = req.body;
  const validReq = validRequest([id, pwd, name]);

  if (!validReq) {
    res.status(400).send("올바른 가입정보를 입력하세요");
    return;
  }

  const existId = db.get(id);
  if (existId) {
    res.status(400).send(`이미 존재하는 아이디 입니다. : ${id}`);
    return;
  } else {
    const newUser = {
      pwd,
      name,
    };
    db.set(id, newUser);
    res.status(200).json({ id, ...newUser, message: "회원가입 완료" });
  }
};

module.exports = {
  loginUser,
  signupUser,
};
  • index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const express = require("express");
const app = express();
const PORT = 3000;

// controllers
const userControllers = require("./controllers/user.contorller.js");

app.listen(PORT, () => {
  console.log(`server listen : ${PORT}`);
});

// body Parser
app.use(express.json());

// middlw Ware (logger)
app.use((req, res, next) => {
  const start = Date.now();
  console.log(`start: ${req.method} ${req.url}`);
  next(); // router 실행
  const diffTime = Date.now() - start;
  console.log(`end: ${req.method} ${req.baseUrl}${req.url} ${diffTime}ms`);
});

app.post("/signup", userControllers.signupUser);
  • 추가적으로 body 유효성 검증을 위한 util 함수를 하나 user.controller.js에 작성하였다.
1
2
3
4
5
6
7
8
9
function validRequest() {
  for (const item of [...arguments]) {
    if (typeof item === "undefined") {
      return false;
    }
  }

  return true;
}

로그인 API 작성

  • method : POST
  • user.controller.js
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
31
32
33
34
35
36
37
/** user.controller.js*/

/**
 * user 로그인 API : POST
 * @param {Request} req
 * @param {Response} res
 * @returns {Response}
 */
const loginUser = (req, res) => {
  const { id, pwd } = req.body;
  console.log(typeof pwd);
  const validReq = validRequest([id, pwd]);
  const idExist = db.get(id);

  // 계정정보를 올바르게 입력 안했을때
  if (!validReq) {
    res.status(400).send("올바른 로그인 정보를 입력해주세요");
    return;
  }

  // db가 비어있거나, 일치하는 id 가 없을 때
  if (db.size === 0 || !idExist) {
    res.status(404).send("해당 ID가 존재하지 않습니다.");
    return;
  }

  // ID 존재 상황
  if (idExist) {
    const dbUser = db.get(id);
    console.log(dbUser);
    if (dbUser && dbUser.pwd === pwd) {
      res.status(200).send(`${id} : ${dbUser.name} 님 로그인을 환영합니다.`);
    } else {
      res.status(400).send("비밀번호가 일치하지 않습니다.");
    }
  }
};
  • index.js 작성
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
const express = require("express");
const app = express();
const PORT = 3000;

// controllers
const userControllers = require("./controllers/user.contorller.js");

app.listen(PORT, () => {
  console.log(`server listen : ${PORT}`);
});

// body Parser
app.use(express.json());

// middlw Ware (logger)
app.use((req, res, next) => {
  const start = Date.now();
  console.log(`start: ${req.method} ${req.url}`);
  next(); // router 실행
  const diffTime = Date.now() - start;
  console.log(`end: ${req.method} ${req.baseUrl}${req.url} ${diffTime}ms`);
});

app.post("/signup", userControllers.signupUser);

// login 
app.post("/login", userControllers.loginUser);

회원 개별 조회 API 작성

  • method : GET
  • user.controller.js
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
/**
 * 회원 개별 조회 API : GET
 * @param {Request} req
 * @param {Response} res
 */
const getUser = (req, res) => {
  const { id } = req.params;
  const dbUser = db.get(id);

  //db에 회원 id가 없는 경우
  if (!dbUser) {
    res.status(404).send(`${id} 회원이 존재하지 않습니다.`);
  }
  const user = {
    id,
    pwd: dbUser.pwd,
    name: dbUser.name,
  };
  res.status(200).json(user);
};

module.exports = {
  loginUser,
  signupUser,
  getUser,
};
  • index.js
1
app.get("/users/:id", userControllers.getUser);

개별 회원 삭제 API

  • method : POST
  • user.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * 개별 회원 탈퇴 API : DELETE
 * @param {Request} req
 * @param {Response} res
 */
const deleteUser = (req, res) => {
  const { id } = req.params;
  const validReq = validRequest([id]);
  if (!validReq) {
    res.status(400).send("올바른 id를 입력해주세요");
  }

  const dbUser = db.get(id);

  // 올바른 user 존재 시 삭제
  if (dbUser) {
    const userDeleteState = db.delete(id);
    if (userDeleteState) {
      res.status(200).send(`${id} 회원이 정상적으로 탈퇴 되었습니다.`);
    } else {
      res.status(505).send(`${id}의 탈퇴가 이루어지지 않았습니다.`);
    }
  }
};
  • index.js 라우터 코드 추가
1
app.delete("/users/:id", userControllers.deleteUser);

회원이 좋아하는 artist API 설계

  • 좋아하는 Artist 페이지 생성
  • Artist 페이지 수정
  • Artist 페이지 삭제

pages 의 데이터를 저장하기 위한 js파일

  • model \ pagesData.js
1
2
3
4
5
const pageData = new Map();
pageData.set("admin", { pageName: "admin Page", artistName: "admin" });

module.exports = pageData;

pages.controller.js 코드 작성 (API)

좋아하는 artist 페이지 생성

  • 팬 페이지 생성 : POST‘/pages’
    • req : body (artistName, pageName)
    • res : artist의 팬이 되어 페이지를 생성했습니다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      
        // controllers \ pages.controller.js
        /**
         * 페이지 생성 API : POST | '/pages/'
         * @param {import("express").Request} req
         * @param {import("express").Response} res
         */
        const createPage = (req, res) => {
          const { artistName, pageName, id } = req.body;
          const pageExist = pageData.has(id);
              
          if (!pageExist) {
            pageData.set(id, { artistName, pageName });
            res
              .status(200)
              .json({ id, artistName, pageName, message: `페이지 생성 완료 : ${id}` });
          } else {
            res.status(400).json({ message: "이미 존재하는 페이지 입니다." });
          }
        };
      

artist 팬 페이지 수정

  • 팬 페이지 수정 : PUT‘/pages/:id’
    • req : url (name)
    • res : 페이지명이 성공적으로 수정 되었습니다.

      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
      
              
        // controllers \ pages.controller.js
        /**
         * 페이지 정보 수정 API : PUT | '/pages/:id'
         * @param {import("express").Request}} req
         * @param {import("express").Response} res
         * @returns
         */
        const updatePage = (req, res) => {
          const { id } = req.params;
          const { artistName, pageName } = req.body;
          const existPage = pageData.get(id);
          if (!artistName || !pageName) {
            res.status(400).send("올바른 정보를 입력해주세요.");
            return;
          }
          if (!existPage) {
            res.status(404).send("수정하려는 페이지를 찾을 수 없습니다.");
            return;
          }
              
          pageData.set(id, { artistName, pageName });
          res.status(200).json({
            message: "페이지 정보가 수정되었습니다.",
            id,
            artistName,
            pageName,
          });
        };
      

artist 팬 페이지 삭제

  • 팬 페이지 삭제 : DELETE‘/pages/:id’
    • req : url (name)
    • res : 페이지가 성공적으로 삭제 되었습니다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      
        // controllers \ pages.controller.js
              
        /**
         * 페이지 삭제 API : DELETE | '/pages/:id'
         * @param {import("express").Request} req
         * @param {import("express").Response} res
         */
        const deletePage = (req, res) => {
          const { id } = req.params;
          const existPage = pageData.get(id);
              
          if (!existPage) {
            res.status(404).json({ message: "삭제할 페이지를 찾을 수 없습니다." });
              
            return;
          }
              
          pageData.delete(id);
          res
            .status(200)
            .json({ message: "페이지를 삭제하였습니다.", pageId: existPage.id });
        };
      

내 페이지 전체 조회

  • 페이지 전체 조회 : GET‘/pages’
    • req : X
    • res : 페이지 전체 데이터

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      
        // controllers \ pages.controller.js
              
        /**
         * pages 전체 조회 API
         * @param {import("express").Request} req
         * @param {import("express").Response} res
         */
        const getPages = (req, res) => {
          const pages = [];
          [...pageData.keys()].forEach((key) => {
            const page = {
              id: key,
              pageName: pageData.get(key).pageName,
              artistName: pageData.get(key).artistName,
            };
            pages.push(page);
          });
          res.status(200).json(pages);
        };
              
      

페이지 개별 조회

  • 개별 페이지 조회 : GET‘/pages/:id’
    • req : url (name)
    • res : 페이지 개별 데이터

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      
        // controllers \ pages.controller.js
        /**
         * 한 페이지 조회 API : GET | '/pages/:id'
         * @param {import("express").Request} req
         * @param {import("express").Response} res
         */
        const getOnePage = (req, res) => {
          console.log(req.params);
          const { id } = req.params;
          const page = pageData.get(id);
              
          if (!page) {
            res.status(404).json({ message: "페이지를 찾을 수 없습니다." });
          } else {
            const pageInfo = { id, ...page };
            res.status(200).json(pageInfo);
          }
        };
      

서버 라우팅을 위한 코드

  • index.js

    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
    
      const express = require("express");
      const app = express();
      const PORT = 3000;
        
      // controllers
      const userControllers = require("./controllers/user.contorller.js");
        
      // Router
      const pagesRouter = require("./routes/page.router.js");
        
      app.listen(PORT, () => {
        console.log(`server listen : ${PORT}`);
      });
        
      // body Parser
      app.use(express.json());
        
      // middlw Ware (logger)
      app.use((req, res, next) => {
        const start = Date.now();
        console.log(`start: ${req.method} ${req.url}`);
        next(); // router 실행
        const diffTime = Date.now() - start;
        console.log(`end: ${req.method} ${req.baseUrl}${req.url} ${diffTime}ms`);
      });
        
      // pages 라우터
      app.use("/pages", pagesRouter);
    
  • routes\page.router.js

    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
    
      // routes/page.router.js
        
      const express = require("express");
        
      const pagesRouter = express.Router();
      const pagesController = require("../controllers/pages.controller.js");
      /**
       * /pages 라우터
       * - get : pages 전체 정보 조회
       * - post : pages 생성
       */
      pagesRouter
        .route("/")
        .get(pagesController.getPages)
        .post(pagesController.createPage);
        
      // /pages/:id 라우터
      // get
      // post
      pagesRouter
        .route("/:id")
        .get(pagesController.getOnePage)
        .put(pagesController.updatePage)
        .delete(pagesController.deletePage);
        
      module.exports = pagesRouter;
    

코드를 작성하면서 느낀점

  • Routing을 지금 내수준에서 분리할 만큼 하고나니까, 처음에는 적응이 어려웠는데, 계속 보다보니 확실히 수정할 부분을 쉽게 찾을 수 있었다.
    • 분리원칙 을 조금 찾아보고 적용했다!
  • 데이터베이스 역할을 하는 Map 객체를 사용 했는데, Map에 대한 method 들을 잘 몰랐지만 MDN 으로 검색해서 이것저것 알게 되었다.
    • delete, set, get
      • set 은 심지어 재할당 기능까지 있는 것을 보고 놀랐다.
  • 지금 같이 간단한 백엔드 코드도 300줄 정도가 넘어가는데, 실제 서비스로 운영 되고 있는 백엔드 코드들은 얼마나 장황할지가 궁금하다…
  • 백엔드도 상당히 재밌다.
  • express는 자유도가 높아도 너무 높은것 같다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.