
유효성 검사는 사용자가 입력한 값이 서버 로직을 수행하기에 적절한지 미리 확인하는 과정이다.
예를 들면:
같은 것들을 DB에 넣기 전에 서버에서 먼저 걸러낸다.
잘못된 요청을 초기에 차단하고, '불필요한 DB 접근'을 줄이기 위한 필수 요소이다.
Express에서는 express-validator 모듈을 사용해 유효성 검사를 미들웨어 형태로 처리할 수 있다.
각 필드별로 검증 규칙을 선언하고, validationResult(req)를 통해 결과를 한 번에 확인한다.
const { body } = require('express-validator')
배열 형태로 validator를 나열하면 기본적으로 AND 조건이 된다.
[
body('user_id').notEmpty().isInt(),
body('name').notEmpty()
]
의미:
둘 중 하나라도 실패하면 전체 검증이 실패한다.
OR 조건이 필요할 때는 oneOf()를 사용한다.
const { body, oneOf } = require('express-validator')
oneOf
([
body('user_id').notEmpty(),
body('email').isEmail()
])
의미:
즉 여러 조건 중 하나만 만족하면 통과한다.
검증 결과는 반드시 validationResult(req)로 직접 확인해야 한다.
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
중요한 점은 응답 후 반드시 return으로 흐름을 종료해야 한다는 것이다.
그렇지 않으면 이후 로직이 계속 실행되어 ERR_HTTP_HEADERS_SENT 오류가 발생할 수 있다.
express-validator의 body() 들은 값을 검사해서 에러를 쌓기만 하고,
실제로 요청을 통과시킬지 말지는 판단하지 않는다.
그래서 별도의 validate 미들웨어를 만들어, 앞에서 모인 검증 결과를 한 번에 처리한다.
아래 케이스를 통해 사용법을 알아보자.
validationResult까지 처리validationResult를 미들웨어로 분리(하지만 next가 없으면 통과해도 못 넘어감)next를 받아서 통과 시 다음 단계로 넘김// 1) 라우터 콜백 안에 validationResult까지 한 번에 처리한 형태
// 동작은 하지만, 라우터 코드가 길어지고 재사용이 어렵다
router.route('/')
.post
(
[
body('user_id').notEmpty().withMessage('로그인이 필요한 서비스입니다.').isInt(),
body('name').notEmpty().withMessage('채널명은 필수 입력 값입니다.')
],
(req, res) =>
{
const err = validationResult(req)
if (!err.isEmpty()) { return res.status(400).json({ err: err.array() })}
conn.query(/* ... */)
}
)
// 2) validationResult 처리만 따로 '모듈화'한 validate 미들웨어
// 문제: error가 발생하지 않았다면 다음 로직으로 못 넘어간다.
const validate = (req, res) =>
{
const err = validationResult(req)
if (!err.isEmpty()) { return res.status(400).json(err.array()) }
}
// 3) next를 추가해 통과하면 다음 미들웨어/핸들러로 넘기는 형태
const validate = (req, res, next) =>
{
const err = validationResult(req)
if (err.isEmpty()) { return next() }
return res.status(400).json(err.array())
}
// 먼저 body()들로 에러를 쌓고 → 그 다음 validate가 결과를 판정해야 한다.
router.route('/')
.post(
[
body('user_id').notEmpty().withMessage('로그인이 필요한 서비스입니다.').isInt(),
body('name').notEmpty().withMessage('채널명은 필수 입력 값입니다.'),
validate
],
(req, res) => {
conn.query(/* ... */)
}
)
affectedRows는 DB(MySQL/MariaDB)가 쿼리를 실행한 뒤 돌려주는 결과 객체(results) 안의 값이다.
주로 INSERT / UPDATE / DELETE처럼 '데이터를 변경하는 쿼리'에서 의미가 있으며,
실제로 영향을 받은 row(행)의 개수를 뜻한다.
affectedRows가 왜 필요한걸까?
서버 입장에서는 쿼리를 실행했다고 해서 항상 '성공'이라고 말할 수 없다.
예를 들어 UPDATE/DELETE는 조건(WHERE)에 해당하는 데이터가 없으면,
쿼리는 에러 없이 끝나지만 실제로 바뀐 row는 0개가 될 수 있다.
이때 affectedRows를 보면 아래와 같이 변경사항을 구분할 수 있다.
affectedRows === 1
affectedRows === 0
즉 affectedRows === 0은 "실패"라기보다
바뀐 게 없다(대상이 없거나 변경할 게 없다)에 가깝다.
insertId: INSERT로 생성된 row의 id(자동 증가 PK)changedRows: UPDATE에서 "실제로 값이 달라진" row 수실무에서는 UPDATE/DELETE 성공 여부 판단에 affectedRows를 자주 사용한다.
DB 결과를 바탕으로 HTTP 응답을 만든다.
즉, affectedRows는 서버가 상태 코드를 결정할 때 참고하는 신호다.
conn.query
(
sql, params, (err, results) =>
{
if (err) { return res.status(500).json({ message: 'DB 오류' }) }
if (results.affectedRows === 0) { return res.status(404).json({ message: '대상 데이터 없음' }) }
return res.status(200).json({ message: '정상 처리' })
}
)