본문 바로가기
TIL with Programmers

[TIL] 9/24 express-validator, 유효성 검사, sql 에러, API 우선순위

by 보먀 2024. 9. 24.
728x90
반응형

1. 미니미니 프로젝트 (실습)

 

이전 내용

 

[TIL] 9/6 핸들러, 예외 처리, HTTP 상태코드

1. 핸들러 (Handler)HTTP request 가 날아오면 자동으로 호출되는 메소드 노드에서는 콜백함수로, 콜백함수를 핸들러라고 생각하면 된다. (cf. 스프링에서는 컨트롤러라고 불린다)즉, HTTPMETHOD 를 사용

everydayc0ding.tistory.com

 

 

[TIL] 9/9 API 설계 - 회원 API, 채널 API

1. 미니미니 프로젝트 (실습) 지난번에 했던 미니미니 프로젝트를 이어서 해보려고 한다. 지난번에는 회원 API 의 회원가입 / 회원 개별 조회 / 회원 개별 탈퇴까지 했으니, 로그인 API 를 만들어보

everydayc0ding.tistory.com

 

 

[TIL] 9/23 node.js, db 연결, db 모듈화, SQL 쿼리문, affectedRows, 단축평가(short-circuit evaluation)

1. 미니미니 프로젝트  (실습) 1.1. db 모듈화 db 연결이 필요한 곳마다 연결 코드를 다 쓰는 것은 효율적이지 않기 때문에 module.exports 를 통해 모듈화 시킨다.// mariadb.jsconst mysql = require('mysql2');cons

everydayc0ding.tistory.com

 

 

1.1. 유효성(Validation) 검사

 

사용자가 입력한 값의 유효성(= 타당성)을 확인하는 것

 

express-validator 라는 외부 모듈을 사용해 유효성을 검사할 예정이므로 먼저 설치

npm install express-validation

 

 

여기서 body는 req.body 에 들어있는 값의 유효성을 검증하기 위해 사용되는 메서드이고, validationResult 는 express-validator 모듈에만 존재하는 변수로 오류가 났을 때 오류를 받아주는 메서드이다. 

(body 외에도 param(), query(), header(), cookies() 등의 메서드가 존재한다)

// express-validator 모듈 불러옴
const { body, validationResult } = require('express-validator');

 

body 의 메개변수로 유효성 검증할 필드명을 넣고 .isEmail(), .isString(), isEmpty(), isLength() 등의 다양한 메서드들을 붙여서 유효성을 검사할 수 있다. 필드 여러 개의 유효성을 검사해야 할 경우, 아래와 같이 배열로 묶어서 사용하면 된다. 

 

만약 유효성 검사를 통과하지 못한 경우, validationResult 에 에러 객체에 값이 담기게 되고 errors.isEmpty() 에 걸려 if 문 안에서 처리된다. if 문 안에서는 return 문을 통해 코드가 더 밑으로 내려가지 못하게 처리한다. 

app.post('/user', [
  body('email').isEmail(),
  body('password').isLength({ min: 5 })
], (req, res) => {
  const errors = validationResult(req);
  
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }

  res.send('User data is valid');
});

 

 

1.2. sql 에러

 

유효성 검사 코드를 추가한 개별 채널 생성 코드이고, channels 테이블의 user_id 필드는 users 테이블의 id 필드를 참조하는 FK 이다.

채널을 생성할 때 users 테이블에 존재하지 않는 id 값을 넣고 INSERT 했을 때 실제 데이터베이스 테이블에 데이터가 삽입되지 않지만 에러가 발생하지 않고 성공적으로 처리되었다는 201 Created 상태코드를 뱉는 문제가 발생했다. 

-> 이는 sql 에러 처리를 해주지 않았기 때문에 생기는 문제로 sql 에러 처리가 필요하다. 

// 채널 개별 생성
router
    .route('/')
    .post(
        [body('user_id').notEmpty().isInt().withMessage('숫자 입력 필요'),
         body('name').notEmpty().isString().withMessage('문자 입력 필요')]
        , (req, res) => {
                const err = validationResult(req) // 유효성 검사했는데 에러가 났을 때

                if (!err.isEmpty()) {
                    return res.status(400).json(err.array())
                }
            
                const {name, user_id} = req.body

                let sql = `INSERT INTO channels (name, user_id) VALUES (?, ?)`
                let values = [name, user_id]
                conn.query(sql,values,
                    function(err, results) {
                        res.status(201).json(results)
                    }
                )
    })

 

 

- sql 에러처리

 

sql 쿼리문의 콜백함수의 첫번째 매개변수는 err 로 sql 에러 발생시 에러를 담게 된다. 

sql 에러가 발생한 경우 sql 쿼리문 콜백함수 안에서 if 문을 통해 에러처리를 해주고, 에러가 발생하지 않은 경우 밑으로 내려가 에러처리를 해주면 된다. 

router
    .route('/')
    .post(
        [body('user_id').notEmpty().isInt().withMessage('숫자 입력 필요'),
         body('name').notEmpty().isString().withMessage('문자 입력 필요')]
        , (req, res) => {
                const err = validationResult(req) // 유효성 검사했는데 에러가 났을 때

                if (!err.isEmpty()) {
                    return res.status(400).json(err.array())
                }
            
                const {name, user_id} = req.body

                let sql = `INSERT INTO channels (name, user_id) VALUES (?, ?)`
                let values = [name, user_id]
                conn.query(sql,values,
                    function(err, results) {
                        if (err) { // sql 에러 처리
                            console.log(err)
                            return res.status(400).end()
                        }
                        res.status(201).json(results)
                    }
                )
    }) // 채널 개별 생성

 

 

1.3. API 의 우선순위

 

- 채널 전체 조회: GET /channels

- 채널 개별 조회: GET /channels/:id

 

채널 개별 조회 API 는 url 로 id 값을 받아야 하는데 실수로 id 값을 입력하지 않았다면 포스트맨은 채널 전체 조회 API 와 채널 개별 조회 API 를 어떻게 구별할까?

-> 사실 포스트맨은 구별할 수 있는 방법이 없다. 대신 API 에는 우선순위가 존재하기 때문에 포스트맨은 우선순위가 높은 API 로 인식할 것이다. API 의 우선순위는 코드가 적힌 순서에 따라 결정이 된다. 그래서 채널 전체 조회 API 가 채널 개별 조회 API 보다 위쪽에 적혀있다면 포스트맨은 채널 전체 조회 API 로 인식할 것이고, 채널 개별 조회 API 가 채널 전체 조회 API 보다 위쪽에 적여있다면 포스트맨은 채널 개별 조회 API 로 인식할 것이다. 

 

 

1.4. 채널 API - UPDATE

 

- 채널 개별 수정: PUT /channels/:id

 

UPDATE 쿼리문은 results 로 json 형태의 객체를 반환하기 때문에 존재하지 않는 id 값을 url 로 줬을 때 에러가 나는 것이 아니라 affectedRows 값이 0 인 객체가 반환이 된다. 그래서 수정 사항이 제대로 반영이 되었는지 처리를 하기 위해서는 results.affectedRows 값에 따라 분기하도록 만들어 처리해줘야 한다. 

// 채널 개별 수정
router
    .route('/:id')
    .put(
        [param('id').notEmpty().withMessage('채널 id 필요'),
         body('name').notEmpty().isString().withMessage('채널명 오류')]
        ,(req, res) => {
            const err = validationResult(req)

            if (!err.isEmpty()) {
                return res.status(400).json(err.array())
            }

            let {id} = req.params
            id = parseInt(id)
            let {name} = req.body

            let sql = `UPDATE channels SET name = ? WHERE id = ?`
            let values = [name, id]

            conn.query(sql, values,
                function(err, results) {
                    if (err) {
                        console.log(err)
                        res.status(400).end()
                    }
                    if (results.affectedRows) {
                        res.status(200).json({ message: '수정이 완료되었습니다.' })
                    } else {
                        res.status(400).end()
                    }
                    
                }
            )
        })

 

 

1.4. 채널 API - DELETE

 

- 채널 개별 삭제: DELETE /channels/:id

 

DELETE 쿼리문도 UPDATE 쿼리문과 같이 results 값으로 json 형태의 객체를 반환한다.

그래서 url 로 존재하지 않는 id 값을 보내도 에러가 나지 않고 results 객체에 affectedRows 값이 0인 객체가 반환되기 때문에 UPDATE 쿼리문처럼 results.affectedRows 값에 따라 분기하도록 만들어 처리해줘야 한다. 

router
    .route('/:id')
    .delete(
        param('id').notEmpty().withMessage('채널 id 필요')
        ,(req, res) => {
            const err = validationResult(req)

            if (!err.isEmpty()) {
                return res.status(400).json(err.array())
            }

            let {id} = req.params
            id = parseInt(id)

            const sql = `DELETE FROM channels WHERE id = ?`
            const channel = db.get(id)

            conn.query(sql, id,
                function(err, results) {
                    if (err) {
                        return res.status(400).end()
                    }
                    if (results.affectedRows) {
                        res.status(200).json({ message: '삭제되었습니다.' })
                    } else {
                        res.status(404).end()
                    }
                }
            )
    }) // 채널 개별 삭제

 

728x90
반응형