본문으로 건너뛰기 Yup Complete Guide | Schema Validation for JavaScript

Yup Complete Guide | Schema Validation for JavaScript

Yup Complete Guide | Schema Validation for JavaScript

이 글의 핵심

Yup is a schema-based validation library for JavaScript. It provides expressive schema definitions, async validation, and excellent integration with form libraries.

Introduction

Yup is a JavaScript schema builder for value parsing and validation. It’s widely used with form libraries like Formik and React Hook Form.

Without Yup

function validateUser(data) {
  const errors = {};
  
  if (!data.name) {
    errors.name = 'Name is required';
  } else if (data.name.length < 2) {
    errors.name = 'Name must be at least 2 characters';
  }
  
  if (!data.email) {
    errors.email = 'Email is required';
  } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
    errors.email = 'Invalid email format';
  }
  
  if (!data.age) {
    errors.age = 'Age is required';
  } else if (data.age < 18) {
    errors.age = 'Must be 18 or older';
  }
  
  return Object.keys(errors).length > 0 ? errors : null;
}

With Yup

import * as yup from 'yup';

const userSchema = yup.object({
  name: yup.string().required().min(2),
  email: yup.string().required().email(),
  age: yup.number().required().min(18),
});

await userSchema.validate(data);

1. Installation

npm install yup

2. Basic Schemas

import * as yup from 'yup';

// String
const nameSchema = yup.string();
const emailSchema = yup.string().email();
const urlSchema = yup.string().url();

// Number
const ageSchema = yup.number();
const priceSchema = yup.number().positive();
const quantitySchema = yup.number().integer().min(1);

// Boolean
const acceptedSchema = yup.boolean().oneOf([true]);

// Date
const birthdaySchema = yup.date();
const futureSchema = yup.date().min(new Date());

// Array
const tagsSchema = yup.array().of(yup.string());
const numbersSchema = yup.array().of(yup.number()).min(1).max(5);

// Object
const addressSchema = yup.object({
  street: yup.string().required(),
  city: yup.string().required(),
  zipCode: yup.string().matches(/^\d{5}$/),
});

3. Validation Methods

const schema = yup.object({
  name: yup.string().required(),
  email: yup.string().email().required(),
});

// Validate and throw on error
try {
  await schema.validate({ name: 'Alice', email: 'invalid' });
} catch (error) {
  console.error(error.message);
}

// Validate and return value or undefined
const result = await schema.isValid({ name: 'Alice', email: 'alice@example.com' });
console.log(result); // true

// Validate and get all errors
try {
  await schema.validate({ name: '', email: 'invalid' }, { abortEarly: false });
} catch (error) {
  console.log(error.errors); // Array of all error messages
}

// Validate synchronously (no async rules)
try {
  schema.validateSync({ name: 'Alice', email: 'alice@example.com' });
} catch (error) {
  console.error(error);
}

4. Required and Optional

import * as yup from 'yup';

const schema = yup.object({
  // Required
  name: yup.string().required('Name is required'),
  
  // Optional (nullable)
  middleName: yup.string().nullable(),
  
  // Optional (default value)
  role: yup.string().default('user'),
  
  // Optional (undefined allowed)
  bio: yup.string().notRequired(),
  
  // Conditionally required
  phoneNumber: yup.string().when('contactMethod', {
    is: 'phone',
    then: (schema) => schema.required(),
    otherwise: (schema) => schema.notRequired(),
  }),
});

5. String Validation

yup.string()
  .required('Required')
  .min(2, 'Must be at least 2 characters')
  .max(50, 'Must be less than 50 characters')
  .email('Invalid email')
  .url('Invalid URL')
  .matches(/^[a-zA-Z]+$/, 'Only letters allowed')
  .trim() // Remove whitespace
  .lowercase() // Convert to lowercase
  .uppercase(); // Convert to uppercase

// Custom validation
yup.string().test('is-strong-password', 'Password too weak', (value) => {
  return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(value);
});

6. Number Validation

yup.number()
  .required()
  .min(0, 'Must be non-negative')
  .max(100, 'Must be 100 or less')
  .positive('Must be positive')
  .negative('Must be negative')
  .integer('Must be an integer')
  .lessThan(10)
  .moreThan(0)
  .round('floor'); // 'floor', 'ceil', 'trunc', 'round'

7. Object Validation

const userSchema = yup.object({
  name: yup.string().required(),
  email: yup.string().email().required(),
  address: yup.object({
    street: yup.string().required(),
    city: yup.string().required(),
    country: yup.string().required(),
  }),
  settings: yup.object().shape({
    notifications: yup.boolean().default(true),
    theme: yup.string().oneOf(['light', 'dark']).default('light'),
  }),
});

// Validate
await userSchema.validate({
  name: 'Alice',
  email: 'alice@example.com',
  address: {
    street: '123 Main St',
    city: 'New York',
    country: 'USA',
  },
});

8. Array Validation

// Array of strings
const tagsSchema = yup.array()
  .of(yup.string())
  .min(1, 'At least one tag required')
  .max(5, 'Maximum 5 tags allowed');

// Array of objects
const usersSchema = yup.array().of(
  yup.object({
    id: yup.number().required(),
    name: yup.string().required(),
    email: yup.string().email().required(),
  })
);

// Unique items
const uniqueEmailsSchema = yup.array()
  .of(yup.string().email())
  .test('unique', 'Emails must be unique', (values) => {
    return values.length === new Set(values).size;
  });

await tagsSchema.validate(['react', 'typescript', 'nextjs']);

9. Conditional Validation

const schema = yup.object({
  accountType: yup.string().oneOf(['personal', 'business']).required(),
  
  // Required only for business accounts
  companyName: yup.string().when('accountType', {
    is: 'business',
    then: (schema) => schema.required('Company name is required'),
    otherwise: (schema) => schema.notRequired(),
  }),
  
  // Multiple conditions
  taxId: yup.string().when(['accountType', 'country'], {
    is: (accountType, country) => accountType === 'business' && country === 'US',
    then: (schema) => schema.required('Tax ID required for US businesses'),
  }),
});

10. Custom Validation

// Custom test
const passwordSchema = yup.string()
  .test('strong-password', 'Password must include uppercase, lowercase, and number', 
    (value) => {
      if (!value) return false;
      return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(value);
    }
  );

// Async validation
const emailSchema = yup.string()
  .email()
  .test('unique-email', 'Email already exists', async (value) => {
    const exists = await checkEmailExists(value);
    return !exists;
  });

// Access other fields
const schema = yup.object({
  password: yup.string().required().min(8),
  confirmPassword: yup.string()
    .required()
    .oneOf([yup.ref('password')], 'Passwords must match'),
});

11. TypeScript Integration

import * as yup from 'yup';
import type { InferType } from 'yup';

const userSchema = yup.object({
  name: yup.string().required(),
  email: yup.string().email().required(),
  age: yup.number().required().positive().integer(),
  website: yup.string().url().nullable(),
});

// Infer TypeScript type from schema
type User = InferType<typeof userSchema>;

// Type is:
// {
//   name: string;
//   email: string;
//   age: number;
//   website: string | null;
// }

async function createUser(data: unknown) {
  const user: User = await userSchema.validate(data);
  return user; // Fully typed!
}

12. React Hook Form Integration

npm install react-hook-form @hookform/resolvers
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';

const schema = yup.object({
  name: yup.string().required('Name is required').min(2),
  email: yup.string().required('Email is required').email('Invalid email'),
  age: yup.number().required('Age is required').positive().integer().min(18),
});

function SignupForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: yupResolver(schema),
  });
  
  const onSubmit = (data) => {
    console.log(data); // Validated data
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} />
      {errors.name && <p>{errors.name.message}</p>}
      
      <input {...register('email')} type="email" />
      {errors.email && <p>{errors.email.message}</p>}
      
      <input {...register('age')} type="number" />
      {errors.age && <p>{errors.age.message}</p>}
      
      <button type="submit">Submit</button>
    </form>
  );
}

13. Custom Error Messages

const schema = yup.object({
  name: yup.string()
    .required('—름??—력—주—요')
    .min(2, '—름— 최소 2??—상—어??—니??),
  
  email: yup.string()
    .required('—메—을 —력—주—요')
    .email('—바�?—메??—식??—닙—다'),
  
  age: yup.number()
    .required('—이�?—력—주—요')
    .min(18, '18??—상�?가—할 ??—습—다')
    .max(100, '—이??100??—하—야 —니??),
});

// With interpolation
yup.string()
  .min(5, 'Must be at least ${min} characters')
  .max(20, 'Must be at most ${max} characters');

14. Best Practices

1. Reuse Schemas

// Shared schemas
const emailSchema = yup.string().email().required();
const passwordSchema = yup.string().required().min(8);

const loginSchema = yup.object({
  email: emailSchema,
  password: passwordSchema,
});

const signupSchema = yup.object({
  email: emailSchema,
  password: passwordSchema,
  confirmPassword: yup.string()
    .required()
    .oneOf([yup.ref('password')]),
});

2. Type-Safe Schemas

import * as yup from 'yup';

const createUserSchema = <T extends yup.AnyObject>(extraFields: T) => {
  return yup.object({
    name: yup.string().required(),
    email: yup.string().email().required(),
    ...extraFields,
  });
};

const adminSchema = createUserSchema({
  role: yup.string().oneOf(['admin', 'superadmin']).required(),
});

3. Separate Validation Logic

// validators/user.js
export const userValidators = {
  name: yup.string().required().min(2),
  email: yup.string().required().email(),
  age: yup.number().required().min(18),
};

// forms/signup.js
import { userValidators } from '../validators/user';

const signupSchema = yup.object({
  ...userValidators,
  password: yup.string().required().min(8),
});

Summary

Yup provides powerful schema-based validation:

  • Expressive API for defining schemas
  • TypeScript support with type inference
  • Async validation for server checks
  • Deep nesting for complex objects
  • Great ecosystem integration

Key Takeaways:

  1. Define schemas once, use everywhere
  2. Use InferType for TypeScript types
  3. Custom validation with test()
  4. Conditional validation with when()
  5. Integrates with React Hook Form

Next Steps:

Resources:


Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Yup is a schema-based validation library for JavaScript. It provides expressive schema definitions, async validation, and excellent integration with form libraries.

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers Yup, Validation, JavaScript, TypeScript, Forms, React.