[Node.js], [JS] Joi를 이용한 데이터 Validation

[Node.js], [JS] Joi를 이용한 데이터 Validation

Joi를 이용한 데이터 Validation 🛠️✨


1. Joi란 무엇인가? 🤔

Joi는 JavaScript 애플리케이션에서 데이터 유효성 검사를 쉽게 처리할 수 있도록 도와주는 강력한 라이브러리이다. 입력 데이터가 사전에 정의된 스키마와 일치하는지 검증하며, 이 과정에서 발생하는 오류를 효율적으로 처리할 수 있게 해준다.

🎯 Joi의 주요 특징

  • 스키마 기반 검증: 데이터 구조를 정의하고, 이를 기반으로 입력 데이터를 검증할 수 있다.
  • 유연한 커스터마이징: 내장 메서드를 활용하거나, custom 메서드를 사용해 특정 조건을 추가로 정의할 수 있다.
  • 읽기 쉬운 문법: 간단하고 직관적인 코드로 데이터 검증 로직을 작성할 수 있다.
  • 다양한 데이터 타입 지원: 문자열, 숫자, 배열, 객체 등 다양한 데이터 타입에 대해 정교한 검증이 가능하다.

2. Joi를 사용해야 하는 이유 🌟

현대 애플리케이션은 클라이언트에서 서버로, 서버에서 데이터베이스로 이동하는 많은 데이터가 있다. 이 데이터가 올바르지 않으면 예상치 못한 오류나 보안 취약점으로 이어질 수 있다.

Joi를 사용하면:

  • 입력 데이터가 유효하지 않을 때 즉시 검출할 수 있다. 🚦
  • 비즈니스 로직에서 데이터 검증 코드를 분리해, 유지보수를 쉽게 한다. 🛠️
  • 커스터마이징 가능한 에러 메시지를 통해 사용자 경험을 개선할 수 있다. 💡

3. Joi 설치하기 📥

먼저, 프로젝트에 Joi를 설치해야 한다.


npm install joi

설치 후, 다음과 같이 사용할 수 있다:


import Joi from 'joi';

4. Joi의 기본 사용법 🧑‍🏫

간단한 예제: 사용자 입력 데이터 검증


import Joi from 'joi';

const schema = Joi.object({
    username: Joi.string().min(3).max(30).required(),
    password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')).required(),
    email: Joi.string().email({ tlds: { allow: false } })
});

const data = {
    username: 'Austin',
    password: '12345',
    email: 'austin@example.com'
};

const result = schema.validate(data);

if (result.error) {
    console.error(result.error.details); // 유효성 검사 실패 시
} else {
    console.log('입력 데이터가 유효합니다.'); // 유효성 검사 통과
}

5. 실무 예제: 차량 데이터 검증 🚗

내가 실무에서 사용하는 소스를 에제로, 데이터 스키마를 정의하고 검증하는 과정을 살펴보자.

예제: 차량 입출차 데이터 검증


import Joi from 'joi';

// 차량 입출차 입력 스키마 정의
export const LprInput = Joi.object({
    serial: Joi.string()
        .pattern(/^\d{4}-\d{4}$/)
        .custom((value, helpers) => {
            // 특정 조건 검증: 7번째 문자는 '0' 또는 '1'이어야 함
            if (value.substring(6, 7) !== '0' && value.substring(6, 7) !== '1') {
                return helpers.error('any.invalid');
            }
            return value;
        })
        .required(),
    date: Joi.string().required(), // 날짜 필수
    carNum: Joi.string().required(), // 차량 번호 필수
    carBin: Joi.string().optional().allow(null) // LPR 이미지 빈 값 허용
});

// 데이터 예제
const inputData = {
    serial: '1234-5678',
    date: '2024-12-09',
    carNum: '123가4568',
    carBin: null
};

const validationResult = LprInput.validate(inputData);

if (validationResult.error) {
    console.error(validationResult.error.details); // 검증 실패 시
} else {
    console.log('데이터 검증 성공:', validationResult.value); // 검증 성공 시
}


예제: 차량 정보 스키마 검증

더 복잡한 데이터를 다룰 때도 Joi는 유연하게 사용 가능하다.

// schema.js

export const LprInfo = Joi.object({
    serial: Joi.string()
        .pattern(/^\d{4}-\d{4}$/)
        .custom((value, helpers) => {
            if (value.substring(6, 7) !== '0' && value.substring(6, 7) !== '1') {
                return helpers.error('any.invalid');
            }
            return value;
        })
        .required(),
    cdDistObsv: Joi.string().required(), // 장비 관리번호 필수
    date: Joi.string().required(), // 날짜 필수
    carNum: Joi.string().required(), // 차량 번호 필수
    carBin: Joi.string().optional().allow(null), // LPR 이미지빈 값 허용
    dateConvert: Joi.string().max(14).required(), // 변환된 날짜
    inOut: Joi.string().valid('0', '1').required(), // 입출차 구분
    tableParkCarinOut: Joi.string().required(), // 테이블명 필수
    MR: Joi.string().max(2).required(), // HH 필수
    RegData: Joi.string().max(8).required() // yyyymmdd 필수
});

// 데이터 예제
const infoData = {
    serial: '2001-1101',
    cdDistObsv: '2001',
    date: '2024-12-09',
    carNum: '123나5678',
    carBin: null,
    dateConvert: '20241209120000',
    inOut: '1',
    tableParkCarinOut: 'parking_table',
    MR: '12',
    RegData: '20241209'
};

const infoValidationResult = LprInfo.validate(infoData);

if (infoValidationResult.error) {
    console.error(infoValidationResult.error.details); // 검증 실패 시
} else {
    console.log('데이터 검증 성공:', infoValidationResult.value); // 검증 성공 시
}
// 데이터 예제
const infoData = {
    serial: '2001-1101',
    cdDistObsv: '2001',
    date: '2024-12-09',
    carNum: '123나5678',
    carBin: null,
    dateConvert: '20241209120000',
    inOut: '1',
    tableParkCarinOut: 'parking_table',
    MR: '12',
    RegData: '20241209'
};
// service.js

import * as schema from '#schema/schema.js';

export const service = async (data, onErrorCallback) => {
		try{
				const lprInfo = {
            serial: lprData.serial,
            cdDistObsv: lprData.serial.substring(0, 4),
            date: lprData.date,
            carNum: lprData.carNum,
            carBin: lprData.carBin,
            dateConvert: lib.convertToCompactFormat(lprData.date),
            inOut: lprData.serial.substring(6, 7) === '0' ? '0' : '1',
            tableParkCarinOut: getTableNameByInOut(extractInOut(lprData.serial)),
            MR: `MR${parseInt(lib.convertToCompactFormat(lprData.date).substring(8, 10))}`,
            RegDate: lib.convertToCompactFormat(lprData.date).substring(0, 8),
        };
        
        await schema.LprInfo.validationAsync(lprInfo); // 서비스단에서 validation 검증 실행
		}catch(error){
				console.error('[service] Error!');
				if (onErrorCallback) {
						onErrorCallback(error);
						// validation 검증 에러 발생 시 에러처리를 하여 호출단에서 catch(라우터)
				}
		}
}

6. Joi의 주요 메서드 🔧

  • required(): 필수 값 설정
  • optional(): 선택적 값 설정
  • pattern(regex): 정규식을 기반으로 데이터 검증
  • custom(callback): 사용자 정의 검증 로직 추가
  • valid(...values): 특정 값만 허용
  • allow(value): 특정 값 허용(예: null)

7. 결론 🏁

Joi는 데이터 검증을 간결하고 효과적으로 처리할 수 있는 도구로, 특히 Node.js 환경에서 필수적인 라이브러리이다. 데이터를 유효성 검증하는 과정에서 코드의 가독성과 유지보수를 크게 향상시킬 수 있다.

보통 schema 소스를 별도로 구현하여 해당 소스에서 벨리데이션을 정의하면 서비스, 라우터등의 소스에서 별도로 데이터 검증을 위한 소스가 필요 없게 되어 소스를 깔끔하게 관리할 수 있다.

참고https://joi.dev/

댓글