포스트

2024.05.13 프로그래머스-fantom 프로젝트 유효성 검사 과정 추가

프로그래머스 데브코스 TIL : 2024-05-13(월)

유효성 검사

  • Validation
  • express-vaidator 라이브러리 사용

    [Getting Startedexpress-validator](https://express-validator.github.io/docs/guides/getting-started)
  • 유효성 검사 라이브러리를 통해, params 또는 body 로 전달되는 값에 대한 올바른 타입을 받았는지 확인할 수 있다.

유효성 검사를 통해 params 체크 하기

  • express-validator의 check는 req.params 를 수신하여 검사할 수 있다.
1
2
3
4
5
6
7
8
9
10
// index.js
app
  .route("/users/:email")
  // 개별 회원 정보 조회 Route
  // 개별 회원 탈퇴 (삭제) Route
  .get(
    [check("email").isEmail().withMessage("이메일 주소를 입력해주세요.")],
    userControllers.getUser
  )
 
  • params로 전달된 email 의 값이 email의 형태인지 검사.
  • email 형태가 아니라면 validationResult 변수를 통해 error 유무를 알 수 있다.
  • withMessage내부의 메세지는 에러 발생 시, err.array()의 msg 값으로 지정된다.
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
// user.controller.js
/**
 * 회원 개별 조회 API : GET
 * @param {Request} req
 * @param {Response} res
 */
const { validationResult } = require("express-validator");
 
const getUser = (req, res) => {
  const err = validationResult(req);
  console.log(err.array());
  const { email } = req.params;
  console.log(email);
  let sql = `SELECT * FROM users
  WHERE email = ?`;

  connection.query(sql, email, (err, results, fields) => {
    console.log(results);
    if (err) console.log(err.name, err.message);
    else {
      if (results[0]) {
        res.status(200).json(results[0]);
      } else {
        res.status(404).json({ message: "회원 정보를 찾을 수 없습니다." });
      }
    }
  });
  • validationResult(req)를 통해 err 변수에 유효성 검사 결과 객체를 할당
1
2
3
4
5
6
7
8
9
10
// 잘못된 params 조건 요청시 err 변수의 array() 결과
[
  {
    type: 'field',
    value: '1',
    msg: '이메일 주소를 입력해주세요.',
    path: 'email',
    location: 'params'
  }
]

express-validator로 page생성 API 수정

이전 fantom 프로젝트에서, 팬 페이지를 생성하는 API를 express-validator 라이브러리를 통해 유효성 검사를 진행하는 코드로 변경

  • route \ pages.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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    
      // routes/page.router.js
        
      const express = require("express");
      const { body } = require("express-validator");
        
      const pagesRouter = express.Router();
      const pagesController = require("../controllers/pages.controller.js");
      /**
       * /pages 라우터
       * - get : pages 전체 정보 조회
       * - post : pages 생성
       */
      pagesRouter
        .route("/")
        .get(pagesController.getPages)
        .post(
          [
            body("artist_id")
              .isString()
              .withMessage("아티스트 이름을 문자열로 입력하세요"),
        
            body("user_id").isInt().withMessage("user_id를 숫자형으로 입력해주세요."),
          ],
          pagesController.createPage
        );
        
      // /pages/:id 라우터
      // get
      // post
      pagesRouter
        .route("/:id")
        .get(pagesController.getOnePage)
        .put(pagesController.updatePage)
        .delete(pagesController.deletePage);
        
      module.exports = pagesRouter;
        
    
  • controller \ page.controller.js 의 createPage API 수정 코드

    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 : POST | '/pages/'
       * @param {import("express").Request} req
       * @param {import("express").Response} res
       */
      const createPage = (req, res) => {
        const error = validationResult(req);
        if (!error.isEmpty()) {
          console.log(error.array());
          return res.status(400).json(error.array());
        }
        const { artist_id, user_id } = req.body;
        let sql = `INSERT INTO pages 
        (user_id, artist_id) 
        VALUES 
        (?, ? )`;
        connection.query(sql, [+user_id, artist_id], (err, result) => {
          if (err) {
            console.log(err.name, err.message);
            return res.status(500).json({ message: err.message });
          } else {
            return res
              .status(200)
              .json({ artist_id, user_id, msg: "page create success" });
          }
        });
    
    • req의 params로 들어오는 ‘user_id’ 와 ‘artist_id’ 가 각각의 타입에 부합하지않으면, 해당 요청에 대한 에러 메시지로 지정한 내용(withMessage) 가 출력 되며, 그에 따른 응답 상태 코드도 400 으로 표기 된다.

pages, GET 요청 SQL 연결하여 코드 수정

  • page.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
    38
    
      /**
       * 한 페이지 조회 API : GET | '/pages/:id'
       * @param {import("express").Request} req
       * @param {import("express").Response} res
       */
      const getOnePage = (req, res) => {
      	//유효성 검사
        const validError = validationResult(req);
        if (!validError.isEmpty()) {
          return res.status(400).json(validError.array());
        }
        console.log(req.params);
        const { id } = req.params;
        let sql = `SELECT * FROM pages 
        WHERE id=?`;
        
        connection.query(sql, id, (err, result) => {
          console.log(result[0]);
          if (err) {
            return res
              .status(400)
              .json({ message: `${id} 페이지를 찾을 수 없습니다.` });
          } else if (result[0]) {
            const page = { ...result[0] };
            return res.status(200).json({ ...page });
          } else {
            return res.status(404).json({ message: "페이지를 찾을 수 없습니다." });
          }
        });
        	
      	// SQL 연결 전 페이지 조회 API
        // if (!page) {
        //   res.status(404).json({ message: "페이지를 찾을 수 없습니다." });
        // } else {
        //   const pageInfo = { id, ...page };
        //   res.status(200).json(pageInfo);
        // }
      };
    
  • route 과정에서 유효성 검사 코드 추가

    1
    2
    3
    4
    5
    6
    
      pagesRouter
        .route("/:id")
        .get(
          check("id").isNumeric().withMessage("올바른 페이지 id를 작성해주세요"),
          pagesController.getOnePage
        )
    
    • express-validator를 사용하여 page 개별 조회 시, id params 가 숫자형이 아닐때에 에러메세지를 반환한다.

유효성 검사 코드를 모듈화 하기

  • express-validator를 사용하여 유효성 검사를 하는 과정이 반복 되므로, 해당 로직을 모듈화 시켜야 하는 필요성이 있다.
  • Route 과정에서 검사하는 일종의 유효성 테스트 코드는, 직접 작성하는것이 맞는 것으로 판단된다. (params나 body가 endpoint 마다 다르기 때문)

app.use 의 형태로 express-validator를 이용한 미들웨어 작성을 시도하였는데, 이 순서 자체가 모순이 있었다.

app.use의 미들웨어는 URL 의 정확도에 상관없이 무조건 실행된다는 점을 우선 기억하자

1
2
3
4
5
6
7
8
9
10
11
// index.js 일
app.use((req, res, next) => {
  const error = validationResult(req);
  if (!error.isEmpty()) {
    return res.status(400).json(error.array());
  }
  next();
});

// pages 라우터
app.use("/pages", pagesRouter);
  • 위의 코드에서 app.use로 지정된 미들웨어가 라우터보다 먼저 실행이 된다.
  • 때문에 미들웨어 부분을 라우팅 하는 코드의 최하단으로 배치하여보았다.
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// index.js
const express = require("express");
// validator library
const { check, validationResult } = require("express-validator");
const app = express();
const PORT = 3000;

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

// Router
const pagesRouter = require("./routes/page.router.js");
const { validRequest } = require("./utils/validRequest.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);

// 회원가입 Route
app.post("/signup", userControllers.signupUser);

// 로그인 Route
app.post("/login", userControllers.loginUser);

app
  .route("/users/:email")
  // 개별 회원 정보 조회 Route
  // 개별 회원 탈퇴 (삭제) Route
  .get(
    [check("email").isEmail().withMessage("이메일 주소를 입력해주세요.")],
    userControllers.getUser
  )
  .delete(userControllers.deleteUser);

// 유효성 검증 middleware
app.use((req, res, next) => {
  console.log("미들웨어!");
  const error = validationResult(req);
  if (!error.isEmpty()) {
    return res.status(400).json(error.array());
  }
  next();
});

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
// page.controller.js 중 일부
/**
 * 한 페이지 조회 API : GET | '/pages/:id'
 * @param {import("express").Request} req
 * @param {import("express").Response} res
 */
const getOnePage = (req, res, next) => {
  console.log("get Page API");
  next();
  // const validError = validationResult(req);
  // if (!validError.isEmpty()) {
  //   return res.status(400).json(validError.array());
  // }
  console.log(req.params);
  console.log("Get PAge API 진행");

  const { id } = req.params;
  let sql = `SELECT * FROM pages 
  WHERE id=?`;

  connection.query(sql, id, (err, result) => {
    console.log(result[0]);
    if (err) {
      return res
        .status(400)
        .json({ message: `${id} 페이지를 찾을 수 없습니다.` });
    } else if (result[0]) {
      const page = { ...result[0] };
      return res.status(200).json({ ...page });
    } else {
      return res.status(404).json({ message: "페이지를 찾을 수 없습니다." });
    }
  });
};

  • 특정 페이지 조회에 대한 요청을 전송했을 때에 대한 로그는 다음 과 같다.

    serverlog

    로그를 확인해보면, getPage API 내부에서 next가 끝난다음에 실행 되어야 할 코드가 먼저 실행 되는 것을 확인할 수 있는데,

    나의 생각으로 이는 비동기 코드가 필요한 시점이라고 생각이 든다.

    Node의 처리 프로세스 상, 유효성 검사를 하는 미들웨어는 나중에 실행이 되버리고, 기존에 진행하던 코드흐름을 그대로 가져가는 것을 확인 할 수 있는데,

    이는 setTimeout 사용시, 원하는 코드를 특정시간이 지난후에 사용하게 되는 것과 같은 원리라고 보여진다.

    비동기 코드를 공부한 뒤, 추후 해당 코드를 수정해보는 시간을 가져야겠다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.