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:
- Define schemas once, use everywhere
- Use InferType for TypeScript types
- Custom validation with test()
- Conditional validation with when()
- Integrates with React Hook Form
Next Steps:
- Try Zod
- Use with React Hook Form
- Build Forms
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.
Related Articles (Internal Links)
Other articles related to this topic.
- Zod Complete Guide: TypeScript-First Schema Validation Library
- Joi Complete Guide
- Immer Complete Guide | Immutable State Made Easy
Keywords Covered in This Article (Related Search Terms)
This article covers Yup, Validation, JavaScript, TypeScript, Forms, React.