TIL

23/11/19 TIL __ Joi ๋ฅผ ํ™œ์šฉํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ feat. Joi๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 

GABOJOK 2023. 11. 19. 23:51

 

 

๐Ÿง ์ƒํ™ฉ

 

๊ฐœ์ธ๊ณผ์ œ์—์„œ ์—๋Ÿฌ์ฒ˜๋ฆฌ์™€ ๋”๋ถˆ์–ด ์‹œ๋„ํ•ด ๋ณธ๊ฒŒ ์žˆ๋‹ค.

์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•ด์ฃผ๋Š” ์ข‹์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์žˆ๋‹ค๊ณ  ํ•ด์„œ ๋‹ค์–‘ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค ์ค‘  Joi๋ฅผ ์‹œ๋„ํ•ด ๋ดค๋‹ค.

Joi๋ฅผ ์‹œ๋„ํ•œ ์ด์œ ๋Š” ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ  ๋•Œ๋ฌธ์ด์˜€๋‹ค.

 

 

ํ•˜์ง€๋งŒ Joi ์™ธ์—๋„ ๋‹ค์–‘ํ•œ validation ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์กด์žฌํ•œ๋‹ค. 

Joi ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•˜์—ฌ, ๋ฐ์ดํ„ฐ ๊ฐ์ฒด์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•œ๋‹ค.
express-validator Express ์›น ํ”„๋ ˆ์ž„ ์›Œํฌ์— ํŠนํ™”. ์ฃผ๋กœ HTTP ์š”์ฒญ์˜ ๋ฐ์ดํ„ฐ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•œ๋‹ค.
Validator.js ๋ฌธ์ž์—ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ํŠนํ™”. 

 

 

express-validator๋„ ๋‹ค๋ค„๋ณด๊ณ  ์‹ถ์ง€๋งŒ, ์˜ค๋Š˜์€ Joi์— ๋Œ€ํ•ด์„œ๋งŒ ๋‹ค๋ค„๋ณด๊ฒ ๋‹ค.

 

 

 

 

๐Ÿฃ์•ก์…˜

 

๋จผ์ € Joi๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•œ๋‹ค. 

npm i joi

 

 

 

๋‚˜์˜ ๊ฒฝ์šฐ joi๋ฅผ ๋‹ค๋ฅธ ํŒŒ์ผ๋กœ ๊ตฌ๋ถ„ํ•ด์„œ ์‚ฌ์šฉํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ  JoiValidation.js ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ , ๊ทธ ์•ˆ์—์„œ ์„ค์น˜ํ•œ joi๋ฅผ require ํ•ด์™”๋‹ค.

 

์•„๋ž˜๋Š” Joi ๊ณต์‹๋ฌธ์„œ์—์„œ ๊ฐ€์ ธ์˜จ ๋‚ด์šฉ์— ์ฃผ์„์„ ์ฒจ๋ถ€ํ•œ ์ฝ”๋“œ์ด๋‹ค.

const Joi = require('joi');

const schema = Joi.object({
    username: Joi.string()
        .alphanum()
        .min(3)
        .max(30)
        .required(),  // required()๋Š” ํ•„์ˆ˜๋กœ ์ ์–ด์•ผ ํ•˜๋Š” ์ปฌ๋Ÿผ์—๋งŒ ์ ์–ด์ฃผ์ž.

    password: Joi.string()
        .pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),

    repeat_password: Joi.ref('password'),  //ref()๋ฅผ ์ด์šฉํ•ด ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ๋ฐ์ดํ„ฐ์™€ ๋™์ผํ•œ์ง€ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.

    email: Joi.string()
        .email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } })
})


try { 
	// Joi์—์„œ ์ œ๊ณตํ•˜๋Š” validateAsync๋ฅผ ์ด์šฉํ•ด ํ•ด๋‹น ์Šคํ‚ค๋งˆ๋ฅผ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.
    const value = await schema.validateAsync({ username: 'abc', birth_year: 1994 });
}
catch (err) { 

}

 

 

์Œ ๋ณ„๊ฑฐ ์—†๋„ค ํ•˜๊ณ  ์ž‘์„ฑ์„ ์‹œ์ž‘ํ–ˆ์ง€๋งŒ, ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ๋‚œ๊ด€์ด ์žˆ์—ˆ๋‹ค.

๊ฐ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์—๋Ÿฌ๋ฉ”์„ธ์ง€ ์ฒ˜๋ฆฌ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด์ค˜์•ผ ํ• ์ง€ ๋ง‰๋ง‰ํ–ˆ๋‹ค. 

 

if๋ฌธ์œผ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผํ• ๊นŒ ๊ณ ๋ฏผํ–ˆ์ง€๋งŒ,

๊ทธ๋Ÿผ joi๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๊ฐ€ ์—†๋Š” ๋“ฏ ํ–ˆ๋‹ค.

 

 

Joi error message ๋ผ๊ณ  ๊ฒ€์ƒ‰ํ•˜์ž ์Šคํ…์˜ค๋ฒ„ํ”Œ๋กœ์šฐ ์—์„œ ์›ํ•˜๋Š” ๋‹ต์„ ์–ป์„ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์•„๋ž˜๋Š” ์ฐธ๊ณ ํ•œ ๋‹ต๋ณ€์ด๋‹ค.

const Joi = require('@hapi/joi');

const joiSchema = Joi.object({
  a: Joi.string()
    .min(2)
    .max(10)
    .required()
    .messages({
      'string.base': `"a" should be a type of 'text'`,
      'string.empty': `"a" cannot be an empty field`,
      'string.min': `"a" should have a minimum length of {#limit}`,
      'any.required': `"a" is a required field`
    })
});

const validationResult = joiSchema.validate({ a: 2 }, { abortEarly: false });
console.log(validationResult.error); // expecting ValidationError: "a" should be a type of 'text'

 

 ์œ„์—์„œ ๋ณด๋‹ค์‹ถ์ด

.massages({})๋ฅผ ์ด์šฉํ•ด, error๊ฐ€ ๋‚ฌ์„ ๊ฒฝ์šฐ ์–ด๋–ค ๋ฉ”์„ธ์ง€๋ฅผ ๋ณด์—ฌ์ค„์ง€ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ๋ฉด ๋˜๋Š”๊ฒƒ ๊ฐ™๋‹ค.

 

 

์œ„์— ์ ํ˜€์žˆ๋Š” 'string.min' ๊ฐ™์€ ๋ฌธ์ž๋Š” ์–ด๋””์„œ ๋‚˜์˜จ๊ฑด์ง€ ๊ถ๊ธˆํ•ด, ์—๋Ÿฌ๊ฐ€ ๋‚œ ๊ฒฝ์šฐ res.send(err)๋ฅผ ๋ณด๋‚ด๋ดค๋‹ค.

์•„๋ž˜๋Š” ์ถœ๋ ฅ ๊ฒฐ๊ณผ์ด๋‹ค.

{
  "_original": {
    "nick_name": "๋ฐฉ์–ดw22wssํšŒ",
    "email": "",
    "password": "123123",
    "passwordRe": "123123",
    "birth_date": "1995-06-24",
    "address": "dd"
  },
  "details": [
    {
      "message": "\"email\" is not allowed to be empty",
      "path": [
        "email"
      ],
      "type": "string.empty",
      "context": {
        "label": "email",
        "value": "",
        "key": "email"
      }
    }
  ]
}

 

๋ณด๋‹ˆ err.details[0].type์˜ ๋‚ด์šฉ๋ฌผ์„ ์ ์–ด์ฃผ๊ณ , ๊ทธ๊ฒƒ์— ํ•ด๋‹นํ•˜๋Š” ๋ฉ”์„ธ์ง€๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ๋“ฏ ํ–ˆ๋‹ค.

 

 

๊ทธ๋Ÿผ try์—์„œ validateAsync()๋ฅผ ํ•ด์ฃผ๊ณ , ์—๋Ÿฌ๊ฐ€ ๋‚œ ๊ฒฝ์šฐ  catch(err)๋กœ ๋„˜์–ด์˜ค๋ฉด์„œ,

res.json์œผ๋กœ  ํ•ด๋‹น ๋ฉ”์„ธ์ง€๋ฅผ ๋ณด๋‚ด์ฃผ๋ฉด ๋ ๊ฒƒ ๊ฐ™์•„ ์‹œ๋„ํ•ด ๋ดค๋‹ค.

 

 

๊ทธ๋ฆฌ๊ณ ,

์›ํ•˜๋Š” ๋Œ€๋กœ ๊ฒฐ๊ณผ๊ฐ€ ์ž˜ ์ถœ๋ ฅ์ด ๋˜์—ˆ๋‹ค!!!

 

 

 

์•„๋ž˜๋Š” ์‹ค์ œ๋กœ ๊ตฌํ˜„ํ•œ Joi๋ฅผ ํ™œ์šฉํ•œ ํšŒ์›๊ฐ€์ž… ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ฝ”๋“œ์ด๋‹ค.

const Joi = require('joi');

//ํšŒ์›๊ฐ€์ž… ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
const registerValidation = async (req, res, next) => {
	const schema = Joi.object({
		nick_name: Joi.string().min(2).max(30).required().messages({
			'string.empty': '๋‹‰๋„ค์ž„์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.'
		}),
		email: Joi.string()
			.email({ minDomainSegments: 2, maxDomainSegments: 2, tlds: { allow: ['com', 'net'] } })
			.required()
			.messages({
				'string.email': '์ด๋ฉ”์ผ์„ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.',
				'string.empty': '์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”'
			}),
		password: Joi.string().min(6).required().messages({
			'string.empty': '๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”',
			'string.min': '๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 6์ž๋ฆฌ ์ด์ƒ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.'
		}),
		passwordRe: Joi.any().valid(Joi.ref('password')).required().messages({
			'any.only': '๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.'
		}),
		birth_date: Joi.date().allow('').iso().min('1-1-1920').max('1-1-2021').messages({
			'date.format': '1920 ~ 2020 ์ถœ์ƒ์ž๋งŒ ์ž…๋ ฅ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. (๋…„-์›”-์ผ ํ˜•์‹).'
		}),
		address: Joi.string().allow('')
	});
	try {
		await schema.validateAsync(req.body);
		next();
	} catch (err) {
		const message = err.details[0].message;
		res.status(400).json({ message });
		next(err);
	}
};

module.exports = registerValidation

 

 

 

๐Ÿฃ ๋Š๋‚€์ 

 

 

Joi๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŽธํ•˜๋‹ค๊ณ  ํ•˜๋Š” ์ด์œ ๋ฅผ ์ด์ œ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

์˜์–ด๊ถŒ์ด๋ผ๋ฉด ํ›จ์”ฌ ํŽธํ–ˆ๊ฒ ์ง€๋งŒ, ํ•œ๊ตญ์— ์‚ด๊ณ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ปค์Šคํ…€ ๋ฉ”์„ธ์ง€๋ฅผ ์ ์–ด์•ผ ํ–ˆ๋˜ ์ ์€ ์•ฝ๊ฐ„ ์•„์‰ฌ์› ๋‹ค.

๊ทธ๋ž˜๋„ ์ผ๊ด„์ ์œผ๋กœ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ณ , ๋ณด๊ธฐ ํŽธํ–ˆ๋‹ค.

๋˜ํ•œ ๊ฐ’ ์ž…๋ ฅ์— ๋Œ€ํ•œ ํ˜•์‹ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋Š” Joi๋งŒ ๊ด€๋ฆฌํ•˜๋Š” ํ•˜๋‚˜์˜ ํŒŒ์ผ ์•ˆ์—์„œ ๊ตฌ๋™๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ด€๋ฆฌ๊ฐ€ ํŽธํ•˜๋‹ค๊ณ  ๋Š๊ปด์กŒ๋‹ค.

 

 

๋‹ค๋ฅธ validation๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์–ด๋–จ์ง€ ๊ถ๊ธˆํ•˜๋‹ค. 

๋‹ค์Œ์— ๊ธฐํšŒ๊ฐ€ ๋œ๋‹ค๋ฉด  express-validator๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด๊ณ  ์‹ถ๋‹ค.

 

 

 

 

 

์ฐธ๊ณ ํ•œ ์‚ฌ์ดํŠธ

https://stackoverflow.com/questions/48720942/node-js-joi-how-to-display-a-custom-error-messages

 

Node.js + Joi how to display a custom error messages?

It seems pretty straight forward to validate user's input in Node.js RESTapi with Joi. But the problem is that my app is not written in English. That means I need to send a custom written messages...

stackoverflow.com

 

https://joi.dev/api/?v=17.9.1

 

joi.dev

 

joi.dev

https://velog.io/@kwontae1313/joi%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%9C%A0%ED%9A%A8%EC%84%B1%EA%B2%80%EC%82%AC

 

joi๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์œ ํšจ์„ฑ๊ฒ€์‚ฌ

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์— joi๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•œ ์œ ํšจ์„ฑ๊ฒ€์‚ฌ์™€ ๊ทธ์— ๋•Œ๋ฅธ ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ์ฝ”๋”ฉ ์ดˆ๊ธฐ if๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ฒŒ๋˜๋ฉด ๊ทธ ์ฝ”๋“œ์˜ ๊ธธ์ด๋‚˜ ์–‘์ด ๋งŽ์ด ๋Š˜์–ด๋‚˜๊ณ , ์ฝ”๋“œ

velog.io