데브코스 웹풀스택 과정/TIL

1001 express-validator 사용해 유효성 검사 / 검사결과 처리 미들웨어 분리

thinktank911 2025. 10. 1. 12:40

유효성검사를 모듈을 사용해서 하는 방법이 있다니 새롭다. 노드에서 제공하는 express-validator 미들웨어가 바로 그 주인공인데 익혀두면 많이 유용할 것 같다.


유효성 검사

validation

  • 사용자가 입력한 값에 대한 유효성(=타당성)을 확인하는 것
    • userId : 값 있어야, 숫자
    • 사람 name : 숫자 x, 문자열, 2자 이상 ...

 

외부 모듈 "express-validator"

 

Getting Started | express-validator

One of the best ways to learn something is by example!

express-validator.github.io

 

 

express-validator는 다양한 요청 데이터에 대한 유효성 검사를 제공한다.

  • check
    가장 일반적인 유효성 검사 함수이다. 이 메소드는 요청 객체의 어떤 부분이든 검사할 수 있다.
  • body
    : HTTP 요청의 body 부분에서만 작동한다. 요청된 바디에 text가 있고, text에 대한 유효성을 검사하고 싶으면 body('text')를 사용하면 된다.
  • param
    : url의 경로 매개변수를 검사하는 데 사용된다. 예를 들어, /user/:id와 같은 url에서 id 매개변수의 유효성 검사를 하려면 param('id')를 사용하면 된다.
  • query
    : url의 특정 쿼리 문자열에서 유효성 검사를 한다. 예를 들어, /search?query=something의 query 유효성 검사를 진행하고 싶다면, query('query')를 사용하면 된다.
  • headers
    : 이 메소드는 헤더에서 유효성 검사를 한다. 예를 들어, 컨텐츠 유형, 인증 정보 등을 포함할 수 있다. header('Authorization')은 Authorization에 대한 유효성 검사를 한다.

 

validator 사용법 - userId/name 유효성 검사

  • 모듈 셋팅
    • const {요청 데이터, 에러 시 결과값} = require('express-validator')
  • api 메소드의 두번째 인자에 위치(배열 가능)
    • post('경로',유효성검사 코드,(req,res)=>{콜백내용})
    • notEmpty() : 비어있지 않아야 함 / isInt() : 숫자여야 함 / withMessage('메세지') : 이를 어길 시 '메세지'를 array에 담음
// 모듈 셋팅
const {body, param, validationResult} = require('express-validator') 
// 유효성검사 모듈(body: 요청 데이터, validationResult : 에러 시 결과값)

router
.route('/')
.post(
    // validator한테 userId 검사해줘 명령
   [body('userId').notEmpty().isInt().withMessage('숫자 입력 필요!'),
    body('name').notEmpty().isString().withMessage('문자 입력 필요!')
   ] 
    , (req,res)=>{
        const err = validationResult(req)

        if(!err.isEmpty()){
            // console.log(err.array())
            // 리턴으로 if문 종료
            return res.status(400).json(err.array())
        }
  • 유효성검사를 통과하지 못했을 때 에러 처리
    • validationResult의 결과값을 err에 넣고 err에 값이 있으면 res.json으로 err.array()(배열형태)를 리턴해준다.
, (req,res)=>{
    const err = validationResult(req)

    if(!err.isEmpty()){
        // console.log(err.array())
        // 리턴으로 if문 종료
        return res.status(400).json(err.array())
    }

 

sql 에러(err) 처리

  • sql문에서 에러가 나는 경우 콜백함수의 첫번째 인자인 err를 통해 무슨 에러가 났는지 err문을 받을 수 있다.
// INSERT 쿼리문
conn.query(sql, values,
    function (err, results) {
        if(err){
            console.log(err)
            return res.status(400).end()
        }

        res.status(201).json(results)
    }
}
  • 에러 내용
    • 채널 테이블에 데이터를 넣을 때 FK인 user_id 에 회원 테이블에 없는 user_id값을 넣어 발생한 에러이다. FK 제약조건을 어겼다는 메세지가 출력된다.
Error: Cannot add or update a child row: a foreign key constraint fails (Youtube.channels, CONSTRAINT user_id FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE NO ACTION ON UPDATE NO ACTION)
at Packet.asError (c:\devCourse\youtube-demo\node_modules\mysql2\lib\packets\packet.js:740:17)
at Query.execute (c:\devCourse\youtube-demo\node_modules\mysql2\lib\commands\command.js:29:26)
at Connection.handlePacket (c:\devCourse\youtube-demo\node_modules\mysql2\lib\base\connection.js:475:34)
at PacketParser.onPacket (c:\devCourse\youtube-demo\node_modules\mysql2\lib\base\connection.js:93:12)
at PacketParser.executeStart (c:\devCourse\youtube-demo\node_modules\mysql2\lib\packet_parser.js:75:16)
at Socket. (c:\devCourse\youtube-demo\node_modules\mysql2\lib\base\connection.js:100:25)
at Socket.emit (node:events:519:28)
at addChunk (node:internal/streams/readable:561:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:512:3)
at Readable.push (node:internal/streams/readable:392:5) {
code: 'ER_NO_REFERENCED_ROW_2',
errno: 1452,
sqlState: '23000',
sqlMessage: 'Cannot add or update a child row: a foreign key constraint fails (Youtube.channels, CONSTRAINT user_id FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE NO ACTION ON UPDATE NO ACTION)',
sql: 'INSERT INTO channels (channel_name, user_id)\n' +
" VALUES ('재밌는 채널', 50)"
}

채널 리팩토링 + API 우선순위

  • 우선순위는 코드 순서임
  • 에러 시 코드마다 배열 다 뜸

update 리팩토링

  • validator로 param에서 가져온 id, body에서 가져온 name 유효성 검사
  • 유효성검사 에러 처리
  • values에 name과 id를 넣고 쿼리문 실행
  • 에러 시 res 상태코드 400 받고 종료(end())
.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 channel_name = ? WHERE id = ?`
    let values = [name, id]
    // UPDATE 쿼리문
    conn.query(sql, values,
        function (err, results) {
            if(err){
                console.log(err)
                return res.status(400).end()
            }

            // 업데이트 잘 되었는지 유효성 검사
            if(results.affectedRows == 0){
                return res.status(400).end()
            }else{
                res.status(200).json(results)
            }
        }
    );

 

※ res.end()
- res.end()는 보내줄 아무 데이터도 없는데 response를 끝내고 싶을때 사용한다.
ex) res.status(400).end();

- 사실 이 메소드는 사용하지 않아도 된다. 보내줄 데이터가 없을때 사용한다고 하는데, 주로 예를 드는 것이 404 에러처리를 리턴해주어야 할때다.res.json을 쓰나 res.send()를 쓰나 응답을 종료해주는 역할을 하기 때문에 굳이 명시적으로 표시해줄 필요는 없다.

 

  • 업데이트 잘 되었는지 유효성 검사
    • id 등 값을 잘못 넣어 수정이 안 될 때가 있는데 이때, 어떤 에러에도 걸리지 않고 result를 잘 반환해준다.
    • 이때, results에 affectedRows를 가져와 0이면 error 상태코드를 반환해주는 방식으로 에러처리를 해줄 수 있다.
// 업데이트 잘 되었는지 확인
if(results.affectedRows == 0){
    return res.status(400).end()
}else{
    res.status(200).json(results)
}

 

 

유효성검사 미들웨어 분리

 

공통적으로 작성해주는 유효성 검사 분리하기

공통으로 분리하고자 하는 유효성검사 코드

  • 모든 api 메소드에 콜백함수 안에 있는 유효성검사 에러 확인 코드를 공통으로 빼려 한다.
  • 처음에는 공통 함수를 정의해 호출하려 했다.
// 공통 함수 정의
function validate(req, res) {
    const err = validationResult(req)

    if(!err.isEmpty()){
        // 리턴으로 if문 종료
        return res.status(400).json(err.array())
    }
}

// 함수 사용
, (req,res)=>{
	validate(req, res)
  • 그런데 공통함수를 정의해서 호출하는 대신, 파일 내부의 함수를 모듈화(미들웨어화)해서 호출하면 훨씬 코드를 줄일 수 있어 편하다.
// 파일 내부 함수 모듈화 가능 - 미들웨어
const validate = (req, res) => {
    const err = validationResult(req)

    if(!err.isEmpty()){
        // 리턴으로 if문 종료
        return res.status(400).json(err.array())
    }
}
  • 사용법
    • validate를 유효성검사코드 넣는 배열에 위치시킨다.

 

※ Express 라우터의 api 메서드 구조

Express에서는 라우터 핸들러를 실행하기 전에 여러 개의 미들웨어를 순차적으로 실행할 수 있게 설계되어 있다.

예를 들어  .get() 은 (path, ...middleware, handler) 구조

 

<이슈>

validate 미들웨어 분리 시 error가 안났을 때 값을 안넘겨주므로 postman 무한대기 발생

 

next()

 

Express 5.x - API Reference

Access the API reference for Express.js 5.x, detailing all modules, methods, and properties for building web applications with this latest version.

expressjs.com

 

 

  • router.param 중 하나인 next 를 넘겨 다음 할 일(미들웨어, 함수) 로 보내는 역할을 하는 next() 함수를 조건에 맞을 때 리턴해주면 된다.

express-validator는 처음 써보는데 매우 효율적이고 간단해 코드 줄이는 데 도움이 될 것 같다. 공통 함수를 미들웨어로 분리해 사용하는 방법 또한 코드를 경제적으로 쓰는 데 도움이 될만한 정보다. 문법에 익숙해져야겠다.