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.

For complete security implementation, check out how to validate data in Node.js and how to use Joi for validation in Node.js.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team