https://myeongsu0257.tistory.com/229
이전 포스팅까지 api를 설계하면서 body에 담겨온 값들을 그냥 무조건 사용하는 방식으로 했는데 이러한 방식은 좋지않습니다. 따라서 유효성검사에 대해 알아보고 코드에 적용해보겠습니다.
유효성검사
- 유효성 : "사용자가 입력한 값" 유효성(타당성)을 확인하는 것
express-validator
https://express-validator.github.io/docs
npm install express-validator
설치를 완료했으면 코드 맨위에 아래와같이 선언해줍니다.
const {body,validationResult}=require('express-validator')
채널생성 유효성검사
그리고 기존의 채널생성api에서 상단에 코드도 추가해줍니다.
//채널 개별 생성
.post(
[body('userId').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, userId } = req.body
conn.query(
`INSERT INTO channels (name,user_id) VALUES(?,?)`, [name, userId],
function (err, results, fields) {
res.status(201).json(results);
}
)
})
비정상적인 값(userid에 문자열)을 body에 넣었을때 콘솔에 아래처럼 출력됩니다. 따라서 위의코드처럼 userId나 name을 if문에서 예외처리를 해주지 않아도 됩니다. 추가적으로 유효성검사에서 에러가 발생했을때 return을 추가해주어서 함수를 종료해줍니다.(아래를 코드를 읽게하지 않게 하기 위해)
body에서 name과 userId에 형식에 맞지 않은 값을 넣은 경우
sql 에러
현재 DB에 없는 userId를 넣으면 아무런 오류를 리턴해주지 않습니다. 따라서 위의 코드에서 query에 아래의 코드로 수정해줍니다. 즉 err가 발생하면 400에러를 돌려줍니다.
conn.query(
`INSERT INTO channels (name,user_id) VALUES(?,?)`, [name, userId],
function (err, results, fields) {
if(err){
console.log(err);
return res.status(400).end();
}
res.status(201).json(results);
}
)
DB에 없는 userId를 넣은 경우
채널 전체조회 유효성검사
query문안의 if(results.length)이 부분은 사용자의 요청에 대한 내용이 아니기 때문에 if문으로처리했습니다.
//채널 전체 조회
.get(
body('userId').notEmpty().isInt().withMessage('숫자 입력필요')
,(req, res) => {
const err = validationResult(req)
if(!err.isEmpty()){
return res.status(400).json(err.array())
}
let { userId } = req.body
if (userId) {
conn.query(
`SELECT * FROM channels WHERE user_id=?`, userId,
function (err, results, fields) {
if(err){
console.log(err);
return res.status(400).end();
}
if (results.length) {
res.status(200).json(results);
}
else {
notFoundChannel(res)
}
}
)
}
})
채널 개별조회 유효성 검사
채널 개별조회 api는 param로 url의 id를 받아와서 사용합니다. 따라서 코드 맨위에 param을 추가해주고 사용합니다. param은 notEmpty()즉 비어있지만 않으면 됩니다. 어차피 문자열로 들어올 것이고 그걸 숫자로 변환해주고 있기 때문입니다.
...
const { body,param, validationResult } = require('express-validator')
...
//채널개별조회
.get(
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 = Number(id)
conn.query(
`SELECT * FROM channels WHERE id=?`, id,
function (err, results, fields) {
if(err){
console.log(err);
return res.status(400).end();
}
if (results.length) {
res.status(200).json(results);
}
else {
notFoundChannel(res)
}
}
)
})
채널명수정(put)
채널수정입니다. param으로 채널의 id를 받아오고 body에 변경하고 싶은 name을 받아왔습니다. 그리고 sql문을 실행하는데 param에 현재 db에 없는 id를 넣은경우도 200처리가 되길래 아래처럼 afftedRows가 0보다 크면 200을 아니면 400처리를 해주었습니다.
//채널수정
.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 = Number(id);
let {name} = req.body
conn.query(
`UPDATE channels SET name=? WHERE id=?`,[name ,id],
function(err,results,fields){
if(err){
console.log(err)
return res.status(400).end();
}
if(results.affectedRows>0)
{
res.status(200).json(results);
}
else{
res.status(400).end()
}
}
)
})
채널삭제(delete)
채널삭제 delete입니다. put과 마찬가지로 db에없는 채널id를 받은 경우 200처리하는것을 막아주기 위해 afttedRows를 활용했습니다.
//채널삭제
.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 = Number(id)
conn.query(
`DELETE FROM channels WHERE id=?`,id,
function(err,results,fields){
if(err){
console.log(err)
return res.status(400).end();
}
if(results.affectedRows>0)
{
res.status(200).json(results)
}
else{
res.status(400).end()
}
}
)
})
db에 있는 채널 id값을 넣은 경우 affectedRows가 0이아닌 변경된 로우의 숫자를 리턴합니다.
검사 미들웨어 분리(next)
현재 모든 api를 보면 validationResult하는 부분이 중복됩니다. 그래서 이부분을 분리해주겠습니다.
아래처럼 validate라는 함수를 만들어줍니다. 추가한코드는 else인데 else의 next()함수를 사용하지 않으면 validate함수호출이후에 코드 진행이 안됩니다. 그래서 next를 사용해주었습니다.
const validate=(req,res,next)=>{
const err = validationResult(req)
if(!err.isEmpty()){
return res.status(400).json(err.array())
}else{
return next(); //다음 할일(미들웨어,함수)
}
}
유효성검사를 추가한 최종코드입니다.
채널
//express 모듈 셋팅
const express = require('express')
const router = express.Router()
const conn = require('../mariadb')
const { body,param, validationResult } = require('express-validator')
router.use(express.json())
const validate=(req,res,next)=>{
const err = validationResult(req)
if(err.isEmpty()){
return next(); //다음 할일(미들웨어,함수)
}else{
return res.status(400).json(err.array())
}
}
router
.route('/')
//채널 전체 조회
.get(
[
body('userId').notEmpty().isInt().withMessage('숫자 입력필요'),
validate
]
,(req, res,next) => {
let { userId } = req.body
if (userId) {
conn.query(
`SELECT * FROM channels WHERE user_id=?`, userId,
function (err, results, fields) {
if(err){
console.log(err);
return res.status(400).end();
}
if (results.length) {
res.status(200).json(results);
}
else {
notFoundChannel(res)
}
}
)
}
})
//채널 개별 생성
.post(
[body('userId').notEmpty().isInt().withMessage('숫자 입력필요'),
body('name').notEmpty().isString().withMessage('문자 입력필요'),
validate]
, (req, res) => {
const { name, userId } = req.body
conn.query(
`INSERT INTO channels (name,user_id) VALUES(?,?)`, [name, userId],
function (err, results, fields) {
if(err){
console.log(err);
return res.status(400).end();
}
res.status(201).json(results);
}
)
})
router
.route('/:id')
//채널개별조회
.get(
[param('id').notEmpty().withMessage('채널id 필요'),
validate]
,(req, res) => {
let { id } = req.params
id = Number(id)
conn.query(
`SELECT * FROM channels WHERE id=?`, id,
function (err, results, fields) {
if(err){
console.log(err);
return res.status(400).end();
}
if (results.length) {
res.status(200).json(results);
}
else {
notFoundChannel(res)
}
}
)
})
//채널삭제
.delete(
[param('id').notEmpty().withMessage('채널 id 필요'),
validate]
,(req, res) => {
let { id } = req.params
id = Number(id)
conn.query(
`DELETE FROM channels WHERE id=?`,id,
function(err,results,fields){
if(err){
console.log(err)
return res.status(400).end();
}
if(results.affectedRows>0)
{
res.status(200).json(results)
}
else{
res.status(400).end()
}
}
)
})
//채널수정
.put(
[param('id').notEmpty().withMessage('채널id 필요'),
body('name').notEmpty().isString().withMessage('채널명 옲'),
validate]
,(req, res) => {
let { id } = req.params
id = Number(id);
let {name} = req.body
conn.query(
`UPDATE channels SET name=? WHERE id=?`,[name ,id],
function(err,results,fields){
if(err){
console.log(err)
return res.status(400).end();
}
if(results.affectedRows>0)
{
res.status(200).json(results);
}
else{
res.status(400).end()
}
}
)
})
function notFoundChannel(res) {
res.status(404).json({
message: `채널 정보를 찾을 수 없습니다`
})
}
module.exports = router
회원
const express = require('express')
const router = express.Router()
const conn = require('../mariadb')
const {body, param, validationResult} = require('express-validator')
router.use(express.json())
const validate = (req, res, next) => {
const err = validationResult(req)
if (err.isEmpty()) {
return next(); //다음 할일(미들웨어,함수)
} else {
return res.status(400).json(err.array())
}
}
//로그인
router.post('/login',
[
body('email').notEmpty().isEmail().withMessage('이메일 확인필요'),
body('password').notEmpty().isString().withMessage('비밀번호 확인필요'),
validate
]
, (req, res) => {
const { email, password } = req.body
conn.query(
`SELECT * FROM users WHERE email=?`, email,
function (err, results, fields) {
if (err) {
console.log(err);
return res.status(400).end();
}
var loginUser = results[0];
if (loginUser && loginUser.password == password) {
res.status(200).json({
message: `${loginUser.name}님 로그인 되었습니다.`
})
}
else {
res.status(404).json({
message: "이메일 또는 비밀번호가 틀렸습니다."
})
}
}
)
})
//회원가입
router.post('/join',
[
body('email').notEmpty().isEmail().withMessage('이메일 확인필요'),
body('name').notEmpty().isString().withMessage('이름확인필요'),
body('password').notEmpty().isString().withMessage('비밀번호 확인필요'),
body('contact').notEmpty().isString().withMessage('연락처확인필요'),
validate
]
, (req, res) => {
const { email, name, password, contact } = req.body
conn.query(
`INSERT INTO users (email,name,password,contact) VALUES(?,?,?,?)`, [email, name, password, contact],
function (err, results, fields) {
if (err) {
console.log(err);
return res.status(400).end();
}
res.status(201).json(results);
}
)
})
router
.route('/users')
.get(
[
body('email').notEmpty().isEmail().withMessage('이메일 확인필요'),
validate
]
, (req, res) => {
let { email } = req.body;
console.log(email);
conn.query(
`SELECT * FROM users WHERE email=?`, email,
function (err, results, fields) {
if (err) {
console.log(err);
return res.status(400).end();
}
if (results.length) {
res.status(200).json(results);
}
else {
notFoundUsers(res)
}
}
)
})
.delete(
[
body('email').notEmpty().isEmail().withMessage('이메일 확인필요'),
validate
]
, (req, res) => {
let { email } = req.body;
console.log(email);
conn.query(
`DELETE FROM users WHERE email=?`, email,
function (err, results, fields) {
if (err) {
console.log(err);
return res.status(400).end();
}
if (results.length) {
res.status(200).json(results);
}
else {
notFoundUsers(res)
}
}
)
})
function notFoundUsers(res) {
res.status(404).json({
message: `회원 정보를 찾을 수 없습니다`
})
}
module.exports = router
'백엔드 > node.js(express)' 카테고리의 다른 글
도서주문관리 프로젝트 - 1.API 설계(API,테이블) (0) | 2023.12.28 |
---|---|
JWT (0) | 2023.12.26 |
토이프로젝트 유튜브4(회원API,채널API sql적용) (1) | 2023.12.22 |
토이프로젝트 유튜브3(DB생성,express-db연동,db모듈화) (0) | 2023.12.20 |
Docker MariaDB db생성 및 테이블생성(삽입,수정,join) (0) | 2023.12.19 |