How to use Yup for validation in Node.js
Yup is a JavaScript schema validation library that provides intuitive API for validating objects with excellent TypeScript support. As the creator of CoreUI with 12 years of Node.js development experience, I’ve used Yup to validate API requests in applications serving millions of users, appreciating its chainable API and built-in type inference that catches validation errors at compile time.
The most effective approach uses Yup schemas with Express middleware for consistent validation.
Install Yup
npm install yup
Basic Schema Validation
const yup = require('yup')
// Define schema
const userSchema = yup.object({
username: yup.string().min(3).max(30).required(),
email: yup.string().email().required(),
password: yup.string().min(8).required(),
age: yup.number().integer().min(18).max(120).nullable()
})
// Validate data
const userData = {
username: 'johndoe',
email: '[email protected]',
password: 'SecurePass123',
age: 25
}
userSchema.validate(userData)
.then(valid => console.log('Valid:', valid))
.catch(error => console.error('Invalid:', error.message))
// Or with async/await
try {
const valid = await userSchema.validate(userData)
console.log('Valid:', valid)
} catch (error) {
console.error('Invalid:', error.message)
}
Express Middleware
const express = require('express')
const yup = require('yup')
const app = express()
app.use(express.json())
// Validation middleware
const validate = (schema) => async (req, res, next) => {
try {
await schema.validate(req.body, {
abortEarly: false, // Return all errors
stripUnknown: true // Remove unknown properties
})
next()
} catch (error) {
const errors = error.inner.map(err => ({
field: err.path,
message: err.message
}))
res.status(400).json({
success: false,
errors
})
}
}
// Define schemas
const createUserSchema = yup.object({
email: yup.string().email().required(),
password: yup.string().min(8).required(),
username: yup.string().min(3).max(30).required()
})
// Use in route
app.post('/api/users', validate(createUserSchema), (req, res) => {
res.json({ success: true, user: req.body })
})
app.listen(3000)
TypeScript Support
import * as yup from 'yup'
import { InferType } from 'yup'
const userSchema = yup.object({
username: yup.string().required(),
email: yup.string().email().required(),
age: yup.number().integer().positive()
})
// Infer TypeScript type from schema
type User = InferType<typeof userSchema>
// type User = {
// username: string
// email: string
// age?: number
// }
async function createUser(data: User) {
const validData = await userSchema.validate(data)
return validData
}
Nested Objects
const addressSchema = yup.object({
street: yup.string().required(),
city: yup.string().required(),
state: yup.string().length(2).required(),
zipCode: yup.string().matches(/^\d{5}$/).required()
})
const userSchema = yup.object({
name: yup.string().required(),
email: yup.string().email().required(),
address: addressSchema.required(),
contacts: yup.array().of(
yup.object({
type: yup.string().oneOf(['phone', 'email']).required(),
value: yup.string().required()
})
).min(1)
})
const data = {
name: 'John Doe',
email: '[email protected]',
address: {
street: '123 Main St',
city: 'New York',
state: 'NY',
zipCode: '10001'
},
contacts: [
{ type: 'phone', value: '555-0123' }
]
}
await userSchema.validate(data)
Custom Validation Methods
const yup = require('yup')
// Add custom validation method
yup.addMethod(yup.string, 'strongPassword', function() {
return this.test('strong-password', 'Password must contain uppercase, lowercase, number and special character', function(value) {
if (!value) return false
const hasUpperCase = /[A-Z]/.test(value)
const hasLowerCase = /[a-z]/.test(value)
const hasNumber = /\d/.test(value)
const hasSpecial = /[@$!%*?&]/.test(value)
return hasUpperCase && hasLowerCase && hasNumber && hasSpecial
})
})
const schema = yup.object({
password: yup.string().min(8).strongPassword().required()
})
await schema.validate({ password: 'Weak123' })
// Error: Password must contain uppercase, lowercase, number and special character
Conditional Validation
const schema = yup.object({
type: yup.string().oneOf(['personal', 'business']).required(),
// Required only for business type
companyName: yup.string().when('type', {
is: 'business',
then: schema => schema.required(),
otherwise: schema => schema.notRequired()
}),
// Required only for personal type
firstName: yup.string().when('type', {
is: 'personal',
then: schema => schema.required(),
otherwise: schema => schema.notRequired()
})
})
// Valid personal
await schema.validate({
type: 'personal',
firstName: 'John'
})
// Valid business
await schema.validate({
type: 'business',
companyName: 'Acme Corp'
})
Async Validation
const userSchema = yup.object({
email: yup.string().email().required()
.test('unique-email', 'Email already exists', async (value) => {
if (!value) return false
// Check database
const user = await db.findByEmail(value)
return !user
}),
username: yup.string().required()
.test('unique-username', 'Username already taken', async (value) => {
if (!value) return false
const exists = await db.usernameExists(value)
return !exists
})
})
try {
const valid = await userSchema.validate({
email: '[email protected]',
username: 'johndoe'
})
console.log('Valid:', valid)
} catch (error) {
console.error('Validation failed:', error.message)
}
Transform Values
const schema = yup.object({
email: yup.string()
.email()
.lowercase() // Transform to lowercase
.trim() // Remove whitespace
.required(),
age: yup.number()
.transform((value, originalValue) => {
// Parse string to number
return originalValue === '' ? undefined : value
})
.integer()
.min(18),
tags: yup.array()
.transform((value) => {
// Convert comma-separated string to array
if (typeof value === 'string') {
return value.split(',').map(tag => tag.trim())
}
return value
})
.of(yup.string())
})
const result = await schema.validate({
email: ' [email protected] ',
age: '25',
tags: 'javascript, nodejs, react'
})
console.log(result)
// {
// email: '[email protected]',
// age: 25,
// tags: ['javascript', 'nodejs', 'react']
// }
Reusable Schemas
// schemas/user.schema.js
const yup = require('yup')
const addressSchema = yup.object({
street: yup.string().required(),
city: yup.string().required(),
zipCode: yup.string().matches(/^\d{5}$/).required()
})
const createUserSchema = yup.object({
email: yup.string().email().required(),
password: yup.string().min(8).required(),
username: yup.string().min(3).max(30).required(),
address: addressSchema.nullable()
})
const updateUserSchema = yup.object({
email: yup.string().email(),
username: yup.string().min(3).max(30),
address: addressSchema.nullable()
}).noUnknown() // Reject unknown properties
module.exports = {
createUserSchema,
updateUserSchema
}
Best Practice Note
This is how we implement validation across all CoreUI Node.js applications using Yup. Yup provides a clean, chainable API with excellent TypeScript support and type inference. Always validate at API boundaries, use async validation for database uniqueness checks, transform input values for consistency, and leverage TypeScript’s InferType for automatic type generation from schemas. Combine Yup with sanitization for complete security.
For production applications, consider using CoreUI’s Node.js Admin Template which includes pre-configured Yup validation patterns.
Related Articles
For complete security implementation, check out how to validate data in Node.js and how to use Joi for validation in Node.js.



