express-validator 7v

์ •๋ฏผ๊ตยท2023๋…„ 7์›” 14์ผ
0

express-validator

๋ชฉ๋ก ๋ณด๊ธฐ
1/1

๐Ÿ“’Middlewares

โœ”๏ธcheckSchema()

checkSchema(schema: Schema, defaultLocations?: Location[]): ValidationChain[] & ContextRunner

์ฃผ์–ด์ง„ schema๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํ•˜๋Š” validation chains์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  express.js์˜ ๋ผ์šฐํŠธ์—์„œ ๋ฏธ๋“ค์›จ์–ด๋กœ์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ ์š”์ฒญ ๊ฐ์ฒด์˜ ๋ชจ๋“  locations(body, cookies, headers, params, query)์— ์žˆ๋Š” ํ•„๋“œ๋“ค์ด ๊ฒ€์ฆ๋œ๋‹ค.

ํ•˜์ง€๋งŒ ์œ„์— ์†Œ๊ฐœ๋œ locations๋“ค ๋ชฉ๋ก์€ defaultLocations ํŒŒ๋ผ๋ฏธํ„ฐ์— ํŠน์ • location๋“ค์„ ๋ช…์‹œํ•œ ๋ฐฐ์—ด์„ ์ธ์ˆ˜๋กœ ๋„˜๊ฒจ์„œ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

checkSchema(schema, ['body', 'query']);

๊ฐ ๊ฒ€์ฆ๋  ํ•„๋“œ์˜ location์„ ์„ธ๋ถ€ ์กฐ์ •ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, in ํ”„๋กœํผํ‹ฐ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. in ํ”„๋กœํผํ‹ฐ๋Š” defaultLocations ๋งค๊ฐœ๋ณ€์ˆ˜๋ณด๋‹ค ๋” ๋†’์€ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๊ฐ€์ง„๋‹ค.

๐Ÿ“ŒManually running checkSchema()

checkSchema()๋Š” ๋ฏธ๋“ค์›จ์–ด๋ฅผ

๐Ÿ“’ValidationChain

validation chain์€ ๋นŒํŠธ์ธ ๊ฒ€์ฆ์ž, sanitizers, ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ฉ”์„œ๋“œ๋ฅผ ํฌํ•จํ•˜์—ฌ ํŠน์ • ํ•„๋“œ์— ๋Œ€ํ•œ ๊ฒ€์ฆ ๋™์ž‘์„ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

validation chains๋Š” check() ํ•จ์ˆ˜์— ์˜ํ•ด ๋งŒ๋“ค์–ด์ง€๋ฉฐ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‚ฌ์šฉ ์˜ˆ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • express.js ๋ผ์šฐํŠธ์˜ ๋ฏธ๋“ค์›จ์–ด๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • express-validator์˜ ํ•จ์ˆ˜(oneOf() or checkExact() ๋“ฑ)์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋…๋ฆฝ ์‹คํ–‰ ํ˜•ํƒœ๋กœ ๊ฒ€์ฆ์ด ์‹คํ–‰๋˜๋Š” ์‹œ๊ธฐ์™€ ๋ฐฉ๋ฒ•์„ ์™„์ „ํžˆ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค.

โœ”๏ธBuilt-in validators

๐Ÿ“Œ.custom()

custom(validator: (value, { req, location, path }) => any): ValidationChain

์ปค์Šคํ…€ ๊ฒ€์ฆ์ž ํ•จ์ˆ˜๋ฅผ ์ฒด์ธ์— ์ถ”๊ฐ€ํ•œ๋‹ค.

ํ•„๋“œ ๊ฐ’์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ์— ์œ ํšจ ๊ฐ’์œผ๋กœ ํŒ์ •๋œ๋‹ค.

  • ์ปค์Šคํ…€ ๊ฒ€์ฆ์ž๊ฐ€ truthy ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•  ๋•Œ
  • ์ปค์Šคํ…€ ๊ฒ€์ฆ์ž๊ฐ€ resolve๋œ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ

๋งŒ์•ฝ ์ปค์Šคํ…€ ์ƒ์„ฑ์ž๊ฐ€ falsy๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜, rejects๋œ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜, ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋ฉด, ํ•ด๋‹น ํ•„๋“œ ๊ฐ’์€ ์œ ํšจํ•œ ๊ฐ’์ด ์•„๋‹ˆ๋ผ๊ณ  ํŒ์ •๋œ๋‹ค.

๐Ÿ“Œ.exsits()

exists(options?: {
  values?: 'undefined' | 'null' | 'falsy',
  checkNull?: boolean,
  checkFalsy?: boolean
}): ValidationChain

ํ•„๋“œ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒ€์ฆ๊ธฐ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

options.values์— ๋ช…์‹œ๋œ ๊ฐ’์— ๋”ฐ๋ผ ํ•„๋“œ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”์ง€ ์—ฌ๋ถ€๊ฐ€ ๊ฒฐ์ •๋˜๋ฉฐ, ๊ธฐ๋ณธ๊ฐ’์€ undefined๋‹ค.

options.valuesBehavior
undefinedํ•„๋“œ ๊ฐ’์ด undefined๋ผ๋ฉด ํ•„๋“œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผํ•œ๋‹ค.
nullํ•„๋“œ ๊ฐ’์ด undefined ํ˜น์€ null์ด๋ผ๋ฉด ํ•„๋“œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผํ•œ๋‹ค.
falsyํ•„๋“œ ๊ฐ’์ด falsy ๊ฐ’์ด๋ผ๋ฉด('',0,false,null,undefined) ํ•„๋“œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผํ•œ๋‹ค.

options.checkNull๊ณผ options.checkFalsy๋Š” deprecated ์˜ต์…˜์ด๋‹ค. options.values๋กœ null, falsy๋ฅผ ์‚ฌ์šฉํ•˜๋ผ.

๐Ÿšจ.exists()๋Š” ๋‹ค๋ฅธ ๊ฒ€์ฆ๊ธฐ๋‚˜ sanitizers๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ•„์š”ํ•˜๋‹ค.

๐Ÿ“Œ.isArray()

isArray(options?: { min?: number; max?: number }): ValidationChain

ํ•„๋“œ ๊ฐ’์ด ๋ฐฐ์—ด์ธ์ง€ ํ™•์ธํ•˜๋Š” ๊ฒ€์ฆ๊ธฐ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ Array.isArray() ํ•จ์ˆ˜์™€ ์œ ์‚ฌํ•˜๋‹ค.

์˜ต์…˜์œผ๋กœ options.min, options.max๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํ—ˆ์šฉ ๋ฐฐ์—ด ๊ธธ์ด๋ฅผ ์ œํ•œํ•  ์ˆ˜ ์žˆ๋‹ค.

// Verifies that the friends list is an array
body('friends').isArray();

// Verifies that ingredients is an array with length >= 0
body('ingredients').isArray({ min: 0 });

// Verifies that team_members is an array with length >= 0 and <= 10
check('team_members').isArray({ min: 0, max: 10 });

๐Ÿ“Œ.isObject()

isObject(options?: { strict?: boolean }): ValidationChain

ํ•„๋“œ ๊ฐ’์ด ๊ฐ์ฒด์ธ์ง€ ํ™•์ธํ•˜๋Š” ๊ฒ€์ฆ๊ธฐ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด {}, { foo: 'bar'}, new MyCustomClass()๋Š” ์ด ๊ฒ€์ฆ๊ธฐ๋ฅผ ํ†ต๊ณผํ•œ๋‹ค.

strict ์˜ต์…˜์„ false๋กœ ์„ค์ •ํ•˜๋ฉด pure Javascript์˜

typeof value === 'object'(javascript์—์„œ ๋ฐฐ์—ด๊ณผ null์„ typeof ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํƒ€์ž…์„ ํ™•์ธํ•˜๋ฉด object๋กœ ๋‚˜์˜จ๋‹ค.)์™€ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•œ๋‹ค.

๐Ÿ“Œ.isString(), .notEmpty()

isString(): ValidationChain

pure Javascript์˜ typeof value === 'string'๊ณผ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.

notEmpty(): ValidationChain

๋นˆ ๋ฌธ์ž์—ด์ด ์•„๋‹Œ์ง€ ํ™•์ธํ•˜๋Š” ๊ฒ€์ฆ๊ธฐ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. .not().isEmpty()์™€ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.

๊ทธ ์™ธ standard vaildators

Standard validators ํ™•์ธ
guide Standard validators/sanitizers ํ™•์ธ

โœ”๏ธBuilt-in sanitizers(์ •์ œ๊ธฐ)

๐Ÿ“Œ.customSanitizer()

customSanitizer(sanitizer: (value, { req, location, path }) => any): ValidationChain

์ปค์Šคํ…€ ์ •์ œ๊ธฐ ํ•จ์ˆ˜๋ฅผ ์ฒด์ธ์— ์ถ”๊ฐ€ํ•œ๋‹ค. ์ •์ œ๊ธฐ๋ฅผ ๊ฑฐ์ณ ํ•„๋“œ ๊ฐ’์ด ๋ณ€๊ฒฝ๋œ๋‹ค.

app.post('/object/:id', param('id').customSanitizer((value, { req }) => {
  // In this app, users have MongoDB style object IDs, everything else, numbers
  return req.query.type === 'user' ? ObjectId(value) : Number(value);
})), (req, res) => {
  // Handle request
});

๐Ÿ“Œ.default()

default(defaultValue: any): ValidationChain

ํ•„๋“œ ๊ฐ’์ด ๋นˆ ๋ฌธ์ž์—ด, null, undefined, NaN ์ผ ๊ฒฝ์šฐ defaultValue๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.

app.post('/', body('username').default('foo'), (req, res, next) => {
  // 'bar'     => 'bar'
  // ''        => 'foo'
  // undefined => 'foo'
  // null      => 'foo'
  // NaN       => 'foo'
});

๐Ÿ“Œ.toArray()

toArray(): ValidationChain

๋ฐฐ์—ด๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค. ์ด๋ฏธ ๋ฐฐ์—ด์ด๋ฉด ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๋Š”๋‹ค. undefined ๊ฐ’์ด๋ผ๋ฉด ๋นˆ ๋ฐฐ์—ด์ด ๋œ๋‹ค.

๐Ÿ“Œ.toLowerCase(), .toUpperCase()

toLowerCase(): ValidationChain
toUpperCase(): ValidationChain

๋ฌธ์ž์—ด์„ ์†Œ๋ฌธ์ž๋กœ ํ˜น์€ ๋Œ€๋ฌธ์ž๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค. ํ•„๋“œ๊ฐ’์ด ๋ฌธ์ž์—ด์ด ์•„๋‹ˆ๋ผ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๋Š”๋‹ค.

๐Ÿ“Œ๊ทธ ์™ธ Standard sanitizers

Standard sanitizers ํ™•์ธ
guide Standard validators/sanitizers ํ™•์ธ

โœ”๏ธModifiers

๐Ÿ“Œ.bail()

bail(options?: { level: 'chain' | 'request' }): ValidationChain

ํŒŒ๋ผ๋ฏธํ„ฐ options.level์˜ ๊ธฐ๋ณธ๊ฐ’์€ chain์ด๋‹ค.

request๋กœ ์„ค์ •ํ•˜๋ฉด ์ด์ „ ๊ฒ€์ฆ๊ธฐ ์ค‘ ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจํ•œ ๊ฒฝ์šฐ validation chain ์‹คํ–‰์ด ๋ฉˆ์ถ˜๋‹ค.

์‹คํŒจ ์‹œ .bail({level:'request'}) ์ดํ›„์˜ ๊ฒ€์ฆ๊ธฐ๋Š” ์‹คํ–‰๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋’ค์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ ์™ธ๋ถ€ API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปค์Šคํ…€ ๊ฒ€์ฆ๊ธฐ๊ฐ€ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ค.

.bail()์€ ๊ฐ™์€ validation ์ฒด์ธ์—์„œ ์—ฌ๋Ÿฌ๋ฒˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

body('username')
  .isEmail()
  // If not an email, stop here
  .bail()
  .custom(checkDenylistDomain)
  // If domain is not allowed, don't go check if it already exists
  .bail()
  .custom(checkEmailExists);
app.get(
  '/search',
  query('query').notEmpty().bail({ level: 'request' }),
  // If `query` is empty, then the following validation chains won't run:
  query('query_type').isIn(['user', 'posts']),
  query('num_results').isInt(),
  (req, res) => {
    // Handle request
  },
);

๐ŸšจoneOf()์™€ checkExact()๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ request-level bail์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰๋˜๋˜ validation chains๊ฐ€ ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰๋˜์–ด์•ผ ํ•ด์„œ ์‹คํ–‰์ด ๋А๋ ค์งˆ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“Œif()

if(condition: CustomValidator | ContextRunner): ValidationChain

์ด ํ•„๋“œ์— ๋Œ€ํ•ด validation chain์ด ๊ณ„์† ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š”์ง€ ์•„๋‹Œ์ง€ ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

์กฐ๊ฑด์€ ์ปค์Šคํ…€ ๊ฒ€์ฆ๊ธฐ ํ˜น์€ contextRunner ์ธ์Šคํ„ด์Šค์ผ ์ˆ˜ ์žˆ๋‹ค.

body('newPassword')
  // Only validate if the old password has been provided
  .if((value, { req }) => req.body.oldPassword)
  // Or, use a validation chain instead
  .if(body('oldPassword').notEmpty())
  // The length of the new password will only be checked if `oldPassword` is provided.
  .isLength({ min: 6 });

๐Ÿ“Œ.not()

not(): ValidationChain

์ฒด์ธ ๋‚ด์— ์žˆ๋Š” ๋‹ค์Œ ๊ฒ€์ฆ๊ธฐ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ถ€์ •ํ•œ๋‹ค.

check('weekday').not().isIn(['sunday', 'saturday']);

๐Ÿ“Œ.optional()

optional(options?: boolean | {
  values?: 'undefined' | 'null' | 'falsy',
  nullable?: boolean,
  checkFalsy?: boolean,
}): ValidationChain

ํ˜„์žฌ validation chain์„ ์„ ํƒ์‚ฌํ•ญ์œผ๋กœ ํ‘œ์‹œํ•œ๋‹ค.

optional ํ•„๋“œ๋Š” value์— ๋”ฐ๋ผ ๊ฒ€์ฆ์„ ๊ฑด๋„ˆ๋›ด๋‹ค.

์–ด๋–ค ๊ฐ’์ด optinal๋กœ ๊ฐ„์ฃผ๋˜๋Š”์ง€๋Š” options.values์— ๋”ฐ๋ผ ๋‹ค๋ฅด๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ undefined๋กœ ์„ค์ •๋œ๋‹ค.

options.valuesbehavior
undefinedํ•„๋“œ ๊ฐ’์ด undefined ๋ผ๋ฉด optional๋กœ ๊ฐ„์ฃผ๋œ๋‹ค.
nullํ•„๋“œ ๊ฐ’์ด undefined๋‚˜ null์ด๋ฉด optional๋กœ ๊ฐ„์ฃผ๋œ๋‹ค.
falsyํ•„๋“œ ๊ฐ’์ด falsy ๊ฐ’(๋นˆ ๋ฌธ์ž์—ด 0, false, null, undefined)์ด๋ฉด optionaly๋กœ ๊ฐ„์ฃผ๋œ๋‹ค.

nullable, checkFalsy๋Š” deprecated๋‹ค. options.values๋ฅผ nullํ˜น์€ falsy๋กœ ์„ค์ •ํ•ด์„œ ์‚ฌ์šฉํ•˜๋ผ

๐Ÿšจvaildator์™€ nasitizer์™€ ๋‹ฌ๋ฆฌ .optional()์€ ์œ„์น˜์— ์ƒ๊ด€์—†์ด ๊ฐ’์ด ํ•ด์„๋˜๋Š” ๋ฐฉ์‹์— ์˜ํ–ฅ์„ ์ค€๋‹ค. ์ฆ‰, ์ฒด์ธ ์–ด๋””์—์„œ ๋ฐœ์ƒํ•˜๋”๋ผ๋„ ์˜ํ–ฅ์„ ์ค€๋‹ค.

body('json_string').isLength({ max: 100 }).isJSON().optional().
body('json_string').optional().isLength({ max: 100 }).isJSON().

๋‘˜์˜ ์ฐจ์ด๋Š” ์—†๋‹ค.

๐Ÿ“Œ.withMessage()

withMessage(message: any): ValidationChain

์ด์ „ ๊ฒ€์ฆ๊ธฐ์— ์˜ํ•ด ์‚ฌ์šฉ๋  ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์„ค์ •ํ•œ๋‹ค.

๐Ÿ“’ExpressValidator

import { ExpressValidator } from 'express-validator';
new ExpressValidator(
  customValidators?: Record<string, CustomValidator>,
  customSanitizers?: Record<string, CustomSanitizer>,
  options?: {
    errorFormatter: ErrorFormatter<E>;
  }
);

ExpressValidator๋Š” express-validator API๋ฅผ ๋ž˜ํ•‘ํ•œ ํด๋ž˜์Šค๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฐจ์ด์ ์ด ์žˆ๋‹ค.

  1. ๊ฒ€์ฆ ์ฒด์ธ์—์„œ ํ•ญ์ƒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปค์Šคํ…€ ๊ฒ€์ฆ๊ธฐ/์ •์ œ๊ธฐ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
  2. ์ผ๋ถ€ ํ•จ์ˆ˜์— ๊ธฐ๋ณธ์ ์œผ๋กœ ์ ์šฉ๋˜๋Š” ์˜ต์…˜์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

์‚ฌ์šฉ์ž ์ •์˜ ๊ฒ€์ฆ๊ธฐ ๋ฐ ์ •์ œ๊ธฐ" ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ๊ณ 

.errorFormatter๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐ, .validationResult()์— ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ๋ณธ ์˜ค๋ฅ˜ ํฌ๋งคํ„ฐ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

๋‚˜์ค‘์— ๋ฒˆ์—ญ

๐Ÿ“’MatchedData()

โœ”๏ธmatchedData()

import { matchedData } from 'express-validator';
matchedData(req, options?: {
  includeOptionals?: boolean,
  onlyValidData?: boolean,
  locations?: Location[],
})

express-validator๋กœ ๊ฒ€์ฆ ๋ฐ ์ •์ œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•˜๊ณ , ๊ทธ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

app.post(
  '/contact-us',
  [body('email').isEmail(), body('message').notEmpty(), body('phone').optional().isMobilePhone()],
  (req, res) => {
    const result = validationResult(req);
    if (!result.isEmpty()) {
      // handle validation errors
      return res.send('Please fix the request');
    }

    const data = matchedData(req);
    // If phone isn't set:
    // => { email: 'foo@bar.com', message: 'Hi hello' }
    // If phone is set:
    // => { email: 'foo@bar.com', message: 'Hi hello', phone: '+1223334444' }
  },
);

๐Ÿ“ŒWith optional data

๊ธฐ๋ณธ์ ์œผ๋กœ matchedData๋Š” optional์ด๊ฑฐ๋‚˜ request์— ํฌํ•จ๋˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ๋Š” ๋ฐ˜ํ™˜ ๊ฐ์ฒด์— ๋‹ด์ง€ ์•Š๋Š”๋‹ค.

์ด ๋™์ž‘์„ ๋ณ€๊ฒฝํ•˜๋ ค๋ฉด options.includeOptionals์„ true๋กœ ์„ค์ •ํ•œ๋‹ค.

app.post(
  '/contact-us',
  [body('email').isEmail(), body('message').notEmpty(), body('phone').optional().isMobilePhone()],
  (req, res) => {
    const data = matchedData(req, { includeOptionals: true });
    // If phone isn't set:
    // => { email: 'foo@bar.com', message: 'Hi hello', phone: undefined }
  },
);

๐Ÿ“ŒWith invalid data

๊ธฐ๋ณธ์ ์œผ๋กœ matchedData๋Š” ์œ ํšจํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์š”์ฒญ์— ์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๋ฐ˜ํ™˜ ๊ฐ์ฒด์— ๋‹ด์ง€ ์•Š๋Š”๋‹ค.

์ด ๋™์ž‘์„ ๋ณ€๊ฒฝํ•˜๋ ค๋ณ€ options.onlyValidData๋ฅผ false๋กœ ์„ค์ •ํ•œ๋‹ค.

app.post('/signup', body('email').isEmail(), body('password').notEmpty(), (req, res) => {
  const data = matchedData(req, { onlyValidData: false });
  // => { email: 'not_actually_an_email', password: '' }
});

๐Ÿ“ŒSelecting data from specific locations

๊ธฐ๋ณธ์ ์œผ๋กœ matchedData๋Š” ๊ฒ€์ฆ์„ ํ†ต๊ณผํ•œ request์˜ ๋ชจ๋“  locations์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒฐ๊ณผ ๊ฐ์ฒด์— ๋‹ด์•„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ด ๋™์ž‘์„ ์ˆ˜์ •ํ•˜๋ ค๋ฉด options.locations๋ฅผ ์ˆ˜์ •ํ•œ๋‹ค. options.locations๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ์„ค์ •ํ•˜๊ณ  ํ•ด๋‹น locations์— ์†ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๊ฒฐ๊ณผ ๊ฐ์ฒด์— ๋‹ด๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

app.post(
  '/signup',
  [body('email').isEmail(), body('password').notEmpty(), query('subscribe_newsletter').isBoolean()],
  (req, res) => {
    const data = matchedData(req);
    // => { email: 'foo@bar.com', password: '12345', subscribe_newsletter: true }

    const data2 = matchedData(req, { locations: ['query'] });
    // => { subscribe_newsletter: true }
  },
);

๐Ÿ“’Errors and Validation Results

์š”์ฒญ์ด ์œ ํšจํ•œ์ง€, ๋ฐœ์ƒ ๊ฐ€๋Šฅํ•œ ๊ฒ€์ฆ ์—๋Ÿฌ๋Š” ๋ฌด์—‡์ธ์ง€ ์‚ดํŽด๋ณด์ž.

โœ”๏ธvalidationResult()

validationResult(req: Request): Result<ValidationError>

request๋กœ๋ถ€ํ„ฐ ๊ฒ€์ฆ ๊ฒฐ๊ณผ๋ฅผ ์ถ”์ถœํ•œ๋‹ค. ๊ฒ€์ฆ ํ›„์—๋Š” Result ๊ฐ์ฒด๋กœ ๊ฒ€์ฆ ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ž˜ํ•‘ํ•œ๋‹ค.

const { query, validationResult } = require('express-validator');

app.post('/hello', query('person').notEmpty(), (req, res) => {
  const result = validationResult(req);
  // Use `result` to figure out if the request is valid or not
});

๐Ÿ“ŒwithDefaults()

validationResult.withDefaults<T>(options: { formatter?: ErrorFormatter<T> }): ResultFactory<T>

validationResult()์™€ ์œ ์‚ฌํ•œ ์ƒˆ๋กœ์šด ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

์ƒ์„ฑ๋œ ํ•จ์ˆ˜๋Š” ์ฃผ์–ด์ง„ ์˜ต์…˜์„ ๋ฐ˜ํ™˜๋œ Result ๊ฐ์ฒด์˜ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฒฐ๊ณผ์˜ ๊ธฐ๋ณธ ์˜ค๋ฅ˜ ํฌ๋งทํ„ฐ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

const { query, validationResult } = require('express-validator');
const myValidationResult = validationResult.withDefaults({
  formatter: error => error.msg,
});

app.post('/hello', query('person').notEmpty(), (req, res) => {
  const errors = myValidationResult(req).array();
  // => ['Invalid value']
});

โœ”๏ธResultFactory<T>

โœ”๏ธResult<T>

๊ฒฐ๊ณผ ๊ฐ์ฒด๋Š” ์š”์ฒญ์˜ ์ƒํƒœ๋ฅผ ๋‘˜๋Ÿฌ์‹ผ ๋ž˜ํผ๋‹ค. ๊ฒฐ๊ณผ ๊ฐ์ฒด๋Š” ์š”์ฒญ์ด ์œ ํšจํ•œ์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋Š” ๋ช‡ ๊ฐ€์ง€ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

๐Ÿšจํƒ€์ž… ๋งค๊ฐœ ๋ณ€์ˆ˜ T๋Š” .array() ๋ฐ .mapped()์™€ ๊ฐ™์€ ๋ฉ”์„œ๋“œ์—์„œ ๋ฐ˜ํ™˜๋˜๋Š” ์˜ค๋ฅ˜์˜ ํƒ€์ž…์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ValidationError์ด๋ฉฐ, .formatWith()๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“Œ.isEmpty()

isEmpty(): boolean

request๊ฐ€ ๊ฒ€์ฆ ์—๋Ÿฌ๋ฅผ ํฌํ•จํ•˜๋Š”์ง€ ์•„๋‹Œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

๐Ÿ“Œ.formatWith()

formatWith<T>(formatter: ErrorFormatter<T>): Result<T>

๊ฒ€์ฆ ์ƒํƒœ๋ฅผ ๋‹ค์‹œ ๋‘˜๋Ÿฌ์‹ผ ์ƒˆ๋กœ์šด Result ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ƒˆ๋กœ์šด Result ๊ฐ์ฒด๋Š” ์ฃผ์–ด์ง„ formatter๋ฅผ ์—๋Ÿฌ ํฌ๋งคํ„ฐ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

const { query, validationResult } = require('express-validator');

app.post('/hello', query('person').notEmpty(), (req, res) => {
  const result = validationResult(req);
  const errors = result.array();
  // => [{ msg: 'Invalid value', ... }]

  const result2 = result.formatWith(error => error.msg);
  const errors2 = result2.array();
  // => ['Invalid value']
});

๐Ÿ“Œ.array()

array(options?: { onlyFirstError?: boolean }): T[]

๊ฒ€์ฆ๋œ ๋ชจ๋“  ํ•„๋“œ๋กœ๋ถ€ํ„ฐ ๋ฐœ์ƒ๋œ ๋ชจ๋“  ์—๋Ÿฌ๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋งŒ๋“ค์–ด ๋ฐ˜ํ™˜ํ•œ๋‹ค.

const result = validationResult(req).array();
// => [{ msg: 'Invalid value', path: 'field1' }, { msg: 'Invalid value', path: 'field1' }]

options.onlyFirstError๊ฐ€ true๋กœ ์„ธํŒ…๋˜๋ฉด ๊ฐ ํ•„๋“œ์˜ ์ฒซ ๋ฒˆ์งธ ์—๋Ÿฌ๋งŒ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

const result = validationResult(req).array({ onlyFirstError: true });
// => [{ msg: 'Invalid value', path: 'field1' }]

๐Ÿ“Œ.mapped()

mapped(): Record<string, T>

ํ•„๋“œ์™€ ๋ฐœ์ƒํ•œ ์—๋Ÿฌ๋ฅผ ๋งคํ•‘ํ•œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ํ•„๋“œ๋กœ๋ถ€ํ„ฐ ๋ฐœ์ƒํ•œ ์—๋Ÿฌ๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ๋ผ๋ฉด ์ฒซ ๋ฒˆ์งธ ์—๋Ÿฌ๋งŒ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

const result = validationResult(req).mapped();
// => { field1: { msg: 'Invalid value', ... }, field2: { msg: 'Invalid value', ... } }

๐Ÿ“Œ.throw()

throw(): void

๊ฒฐ๊ณผ ๊ฐ์ฒด๊ฐ€ ์—๋Ÿฌ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, ์ด ๋ฉ”์„œ๋“œ๋Š” Result ํƒ€์ž…๊ณผ ๋™์ผํ•œ ๋ฉ”์†Œ๋“œ๋กœ ์žฅ์‹๋œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.(this method throws an error decorated with the same methods as the Result type.)

๋งŒ์•ฝ ๊ฒฐ๊ณผ ๊ฐ์ฒด์— ์—๋Ÿฌ๊ฐ€ ์—†๋‹ค๋ฉด ์ด ๋ฉ”์„œ๋“œ๋Š” ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๋Š”๋‹ค.

app.post('/hello', query('person').notEmpty(), (req, res) => {
  try {
    validationResult(req).throw();
    res.send(`Hello, ${req.query.person}!`);
  } catch (e) {
    res.status(400).send({ errors: e.mapped() });
  }
});

โœ”๏ธError types

express-validator์˜ ๋‹ค์–‘ํ•œ API๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ์ข…๋ฅ˜์˜ ๊ฒ€์ฆ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

๐Ÿ“ŒFieldValidationError

type FieldValidationError = {
  type: 'field';
  location: Location;
  path: string;
  value: any;
  msg: any;
};

๋‹จ์ผ ํ•„๋“œ ๊ฒ€์ฆ์‹œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.

๐Ÿ“ŒAlternativeValidationError

type AlternativeValidationError = {
  type: 'alternative';
  msg: any;
  nestedErrors: FieldValidationError[];
};

์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฒ€์ฆ ์ž‘์—…(ex. in oneOf())์—์„œ ์‹คํŒจํ–ˆ์„ ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค. nestedErrors์—๋Š” ๊ฐœ๋ณ„ ํ•„๋“œ ์˜ค๋ฅ˜์˜ flat list๊ฐ€ ํฌํ•จ๋œ๋‹ค.

๐Ÿ“ŒGroupedAlternativeValidationError

๋ชจ๋“  ๋Œ€์ฒด ๋ฐฉ๋ฒ•(ex. in oneOf())์ด ์œ ํšจํ•˜์ง€ ์•Š์•˜์„ ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. nestedErrors์—๋Š” ๋Œ€์ฒด ๋ฐฉ๋ฒ•๋ณ„๋กœ ๊ทธ๋ฃนํ™”๋œ ๊ฐœ๋ณ„ ํ•„๋“œ ์˜ค๋ฅ˜ ๋ชฉ๋ก์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ“ŒUnknownFieldValidationError

type UnknownFieldsError = {
  type: 'unknown_fields';
  msg: any;
  fields: { path: string; location: Location; value: any }[];
};

ํ•˜๋‚˜ ์ด์ƒ์˜ ํ•„๋“œ๊ฐ€ unknown ํ•„๋“œ์ผ ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.

์ฆ‰, checkExact()๊ฐ€ ์‹คํ–‰๋  ๋•Œ ๊ฒ€์ฆ ์ฒด์ธ์ด ์—†๋Š” ๊ฒฝ์šฐ์ด๋‹ค.

fields์—๋Š” ๋ชจ๋“  unknown ํ•„๋“œ๋“ค์˜ ๊ฒฝ๋กœ์™€ locations, values ๋“ค์–ด์žˆ๋Š” ๋ฆฌ์ŠคํŠธ๋“ค ๋ฐ˜ํ™˜ํ•œ๋‹ค.

๐Ÿ“ŒValidationError

์–ด๋– ํ•œ ๊ฒ€์ฆ ์˜ค๋ฅ˜๋“  ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. type ์†์„ฑ์„ ์‚ดํŽด๋ณด๋ฉด ์‹ค์ œ๋กœ ๋ฐœ์ƒํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฌด์—‡์ธ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

switch (error.type) {
  case 'field':
    // `error` is a `FieldValidationError`
    console.log(error.path, error.location, error.value);
    break;

  case 'alternative':
    // `error` is an `AlternativeValidationError`
    console.log(error.nestedErrors);
    break;

  case 'alternative_grouped':
    // `error` is a `GroupedAlternativeValidationError`
    error.nestedErrors.forEach((nestedErrors, i) => {
      console.log(`Errors from chain ${i}:`);
      console.log(nestedErrors);
    });
    break;

  case 'unknown_fields':
    // `error` is an `UnknownFieldsError`
    console.log(error.fields);
    break;

  default:
    // Error is not any of the known types! Do something else.
    throw new Error(`Unknown error type ${error.type}`);
}

โœ”๏ธErrorFormatter<T>

type ErrorFormatter<T> = (error: ValidationError) => T;

Result#mapped() ๋ฐ Result#array()์™€ ๊ฐ™์€ ๋ฉ”์†Œ๋“œ์—์„œ ๋ฐ˜ํ™˜๋œ ์˜ค๋ฅ˜๋ฅผ ํฌ๋งทํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ํ•จ์ˆ˜๋‹ค.

์ด ํ•จ์ˆ˜๋Š” ํฌ๋งท๋˜์ง€ ์•Š์€ ์˜ค๋ฅ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์ƒˆ๋กœ์šด ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค.

profile
๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž

0๊ฐœ์˜ ๋Œ“๊ธ€