Logo
⚠️ Unsaved
[M]:

Joi 101: Getting Started with Schema Validation

This notebook introduces Joi, a powerful schema validation library for JavaScript. We'll explore how to use Joi to validate objects, forms, API payloads, and configuration data with simple, readable validation rules.

[1]:
// Install Joi using npm
!npm install joi
$ npm install joi

added 6 packages in 7s

28 packages are looking for funding
  run `npm fund` for details
[2]:
// Import Joi
const Joi = require('joi');

// Check if Joi is loaded correctly
console.log("Joi version:", Joi.version, "\n");
Joi version: 17.13.3 
[M]:

1. Basic Validation

Let's start with some basic validation examples to understand how Joi works. Joi allows you to define schemas that describe the shape of your data.

[3]:
// Define a simple schema for a user object
const userSchema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
email: Joi.string().email().required(),
birthYear: Joi.number().integer().min(1900).max(2023),
password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$'))
});
[4]:
// Valid user data
const validUser = {
username: 'johndoe',
email: 'john@example.com',
birthYear: 1990,
password: 'password123'
};

// Validate the user data against the schema
const validResult = userSchema.validate(validUser);
console.log("Valid user validation result:\n");
console.log(validResult, "\n");
Valid user validation result:
{
  value: {
    username: 'johndoe',
    email: 'john@example.com',
    birthYear: 1990,
    password: 'password123'
  }
} 
[5]:
// Invalid user data
const invalidUser = {
username: 'j', // too short
email: 'not-an-email',
birthYear: 1800, // too early
password: 'pw'
};

// Validate the invalid user data
const invalidResult = userSchema.validate(invalidUser);
console.log("Invalid user validation result:\n");
console.log(invalidResult, "\n");
Invalid user validation result:
{
  value: {
    username: 'j',
    email: 'not-an-email',
    birthYear: 1800,
    password: 'pw'
  },
  error: [Error [ValidationError]: "username" length must be at least 3 characters long] {
    _original: {
      username: 'j',
      email: 'not-an-email',
      birthYear: 1800,
      password: 'pw'
    },
    details: [ [Object] ]
  }
} 
[M]:

2. Validation Options

Joi provides several options to customize the validation behavior.

[6]:
// Define a schema with some optional fields
const profileSchema = Joi.object({
name: Joi.string().required(),
age: Joi.number().integer().min(0),
email: Joi.string().email(),
website: Joi.string().uri(),
address: Joi.object({
street: Joi.string(),
city: Joi.string(),
zipCode: Joi.string()
})
});
[7]:
// Data with unknown field
const profileData = {
name: 'Jane Smith',
age: 28,
email: 'jane@example.com',
favoriteColor: 'blue', // This field is not in the schema
address: {
street: '123 Main St',
city: 'Anytown',
zipCode: '12345',
country: 'USA' // This nested field is not in the schema
}
};
[8]:
// By default, Joi will reject unknown fields
const strictResult = profileSchema.validate(profileData);
console.log("Strict validation (default):\n");
console.log(strictResult, "\n");
Strict validation (default):
{
  value: {
    name: 'Jane Smith',
    age: 28,
    email: 'jane@example.com',
    favoriteColor: 'blue',
    address: {
      street: '123 Main St',
      city: 'Anytown',
      zipCode: '12345',
      country: 'USA'
    }
  },
  error: [Error [ValidationError]: "address.country" is not allowed] {
    _original: {
      name: 'Jane Smith',
      age: 28,
      email: 'jane@example.com',
      favoriteColor: 'blue',
      address: [Object]
    },
    details: [ [Object] ]
  }
} 
[9]:
// Allow unknown fields
const allowUnknownResult = profileSchema.validate(profileData, { allowUnknown: true });
console.log("Validation with allowUnknown: true:\n");
console.log(allowUnknownResult, "\n");
Validation with allowUnknown: true:
{
  value: {
    name: 'Jane Smith',
    age: 28,
    email: 'jane@example.com',
    favoriteColor: 'blue',
    address: {
      street: '123 Main St',
      city: 'Anytown',
      zipCode: '12345',
      country: 'USA'
    }
  }
} 
[10]:
// Strip unknown fields
const stripUnknownResult = profileSchema.validate(profileData, { stripUnknown: true });
console.log("Validation with stripUnknown: true:\n");
console.log(stripUnknownResult, "\n");
Validation with stripUnknown: true:
{
  value: {
    name: 'Jane Smith',
    age: 28,
    email: 'jane@example.com',
    address: { street: '123 Main St', city: 'Anytown', zipCode: '12345' }
  }
} 
[11]:
// Abort early (default) vs. collect all errors
const abortEarlyResult = profileSchema.validate(invalidUser);
console.log("Validation with abortEarly: true (default):\n");
console.log(abortEarlyResult, "\n");

const collectAllErrorsResult = profileSchema.validate(invalidUser, { abortEarly: false });
console.log("Validation with abortEarly: false:\n");
console.log(collectAllErrorsResult, "\n");
Validation with abortEarly: true (default):
{
  value: {
    username: 'j',
    email: 'not-an-email',
    birthYear: 1800,
    password: 'pw'
  },
  error: [Error [ValidationError]: "name" is required] {
    _original: {
      username: 'j',
      email: 'not-an-email',
      birthYear: 1800,
      password: 'pw'
    },
    details: [ [Object] ]
  }
} 
Validation with abortEarly: false:
{
  value: {
    username: 'j',
    email: 'not-an-email',
    birthYear: 1800,
    password: 'pw'
  },
  error: [Error [ValidationError]: "name" is required. "email" must be a valid email. "username" is not allowed. "birthYear" is not allowed. "password" is not allowed] {
    _original: {
      username: 'j',
      email: 'not-an-email',
      birthYear: 1800,
      password: 'pw'
    },
    details: [ [Object], [Object], [Object], [Object], [Object] ]
  }
} 
[M]:

3. Advanced Validation Rules

Joi provides a rich set of validation rules for different data types.

[12]:
// String validation
const stringSchema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
email: Joi.string().email().required(),
website: Joi.string().uri(),
hexColor: Joi.string().pattern(/^#[0-9a-f]{6}$/i),
phone: Joi.string().pattern(/^\d{3}-\d{3}-\d{4}$/),
password: Joi.string().min(8).pattern(/[a-zA-Z0-9]/).required(),
confirmPassword: Joi.ref('password')
});
[13]:
// Number validation
const numberSchema = Joi.object({
age: Joi.number().integer().min(0).max(120),
price: Joi.number().precision(2).positive(),
quantity: Joi.number().integer().positive().required(),
rating: Joi.number().min(1).max(5).default(3),
port: Joi.number().port()
});
[52]:
// Date validation
const dateSchema = Joi.object({
birthdate: Joi.date().max('now').required(),
appointmentDate: Joi.date().min('now').required(),
registrationDate: Joi.date().default(Date.now),
graduationYear: Joi.date()
});
Error during execution: Unknown date format yyyy
[15]:
// Boolean validation
const booleanSchema = Joi.object({
isActive: Joi.boolean().required(),
receiveEmails: Joi.boolean().default(true),
agreeToTerms: Joi.boolean().valid(true).required().messages({
'boolean.only': 'You must agree to the terms and conditions'
})
});
[16]:
// Array validation
const arraySchema = Joi.object({
tags: Joi.array().items(Joi.string()).min(1).max(5),
scores: Joi.array().items(Joi.number().min(0).max(100)),
permissions: Joi.array().items(Joi.string().valid('read', 'write', 'admin')),
coordinates: Joi.array().ordered(Joi.number(), Joi.number()).length(2)
});
[17]:
// Object validation
const objectSchema = Joi.object({
user: Joi.object({
id: Joi.string().required(),
name: Joi.string().required()
}).required(),
metadata: Joi.object().pattern(Joi.string(), Joi.any()),
settings: Joi.object().unknown(true)
});
[M]:

4. Conditional Validation

Joi allows you to create schemas with conditional validation logic.

[18]:
// Conditional validation based on another field
const userRegistrationSchema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().min(8).required(),
accountType: Joi.string().valid('personal', 'business').required(),
// Company name is required only for business accounts
companyName: Joi.string().when('accountType', {
is: 'business',
then: Joi.required(),
otherwise: Joi.optional()
}),
// Tax ID is required only for business accounts
taxId: Joi.string().when('accountType', {
is: 'business',
then: Joi.required(),
otherwise: Joi.optional()
})
});
[19]:
// Valid personal account
const personalAccount = {
username: 'johndoe',
password: 'password123',
accountType: 'personal'
};

const personalResult = userRegistrationSchema.validate(personalAccount);
console.log("Personal account validation result:\n");
console.log(personalResult, "\n");
Personal account validation result:
{
  value: {
    username: 'johndoe',
    password: 'password123',
    accountType: 'personal'
  }
} 
[20]:
// Valid business account
const businessAccount = {
username: 'acmecorp',
password: 'securepass123',
accountType: 'business',
companyName: 'ACME Corporation',
taxId: '123-45-6789'
};

const businessResult = userRegistrationSchema.validate(businessAccount);
console.log("Business account validation result:\n");
console.log(businessResult, "\n");
Business account validation result:
{
  value: {
    username: 'acmecorp',
    password: 'securepass123',
    accountType: 'business',
    companyName: 'ACME Corporation',
    taxId: '123-45-6789'
  }
} 
[21]:
// Invalid business account (missing required fields)
const invalidBusinessAccount = {
username: 'acmecorp',
password: 'securepass123',
accountType: 'business'
// Missing companyName and taxId
};

const invalidBusinessResult = userRegistrationSchema.validate(invalidBusinessAccount, { abortEarly: false });
console.log("Invalid business account validation result:\n");
console.log(invalidBusinessResult, "\n");
Invalid business account validation result:
{
  value: {
    username: 'acmecorp',
    password: 'securepass123',
    accountType: 'business'
  },
  error: [Error [ValidationError]: "companyName" is required. "taxId" is required] {
    _original: {
      username: 'acmecorp',
      password: 'securepass123',
      accountType: 'business'
    },
    details: [ [Object], [Object] ]
  }
} 
[M]:

5. Custom Validation Messages

Joi allows you to customize error messages for better user experience.

[22]:
// Custom validation messages
const signupSchema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required().messages({
'string.base': 'Username must be a text value',
'string.empty': 'Username cannot be empty',
'string.min': 'Username must be at least {#limit} characters long',
'string.max': 'Username cannot exceed {#limit} characters',
'string.alphanum': 'Username must only contain alphanumeric characters',
'any.required': 'Username is required'
}),
email: Joi.string().email().required().messages({
'string.email': 'Please enter a valid email address',
'any.required': 'Email address is required'
}),
password: Joi.string().min(8).pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/).required().messages({
'string.min': 'Password must be at least {#limit} characters long',
'string.pattern.base': 'Password must contain at least one uppercase letter, one lowercase letter, and one number',
'any.required': 'Password is required'
})
});
[23]:
// Test with invalid data to see custom messages
const invalidSignupData = {
username: 'a',
email: 'not-an-email',
password: 'weak'
};

const signupResult = signupSchema.validate(invalidSignupData, { abortEarly: false });
console.log("Signup validation with custom messages:\n");
console.log(signupResult, "\n");
Signup validation with custom messages:
{
  value: { username: 'a', email: 'not-an-email', password: 'weak' },
  error: [Error [ValidationError]: Username must be at least 3 characters long. Please enter a valid email address. Password must be at least 8 characters long. Password must contain at least one uppercase letter, one lowercase letter, and one number] {
    _original: { username: 'a', email: 'not-an-email', password: 'weak' },
    details: [ [Object], [Object], [Object], [Object] ]
  }
} 
[24]:
// Format error messages for display
function formatErrors(error) {
if (!error || !error.details) return {};
return error.details.reduce((acc, curr) => {
const key = curr.path[0];
acc[key] = curr.message;
return acc;
}, {});
}

const formattedErrors = formatErrors(signupResult.error);
console.log("Formatted errors for UI display:\n");
console.log(formattedErrors, "\n");
Formatted errors for UI display:
{
  username: 'Username must be at least 3 characters long',
  email: 'Please enter a valid email address',
  password: 'Password must contain at least one uppercase letter, one lowercase letter, and one number'
} 
[M]:

6. API Request Validation

Let's see how Joi can be used to validate API requests in a real-world scenario.

[25]:
// Define schema for query parameters
const querySchema = Joi.object({
page: Joi.number().integer().min(1).default(1),
limit: Joi.number().integer().min(1).max(100).default(10),
sort: Joi.string().valid('asc', 'desc').default('asc'),
filter: Joi.string().optional()
});
[26]:
// Define schema for request body (for POST/PUT requests)
const bodySchema = Joi.object({
title: Joi.string().min(3).max(100).required(),
content: Joi.string().min(10).required(),
tags: Joi.array().items(Joi.string()).min(1).max(5),
published: Joi.boolean().default(false),
authorId: Joi.string().required()
});
[27]:
// Define schema for URL parameters
const paramsSchema = Joi.object({
id: Joi.string().pattern(/^[0-9a-fA-F]{24}$/).required() // MongoDB ObjectId format
});
[28]:
// Function to validate request parts
function validateRequest(req) {
const schemas = { query: querySchema, body: bodySchema, params: paramsSchema };
const result = {};
for (const key of Object.keys(req)) {
if (schemas[key]) {
const { error, value } = schemas[key].validate(req[key], { abortEarly: false });
if (error) {
result.error = error;
break;
}
result[key] = value; // Use the validated and sanitized value
} else {
result[key] = req[key]; // Pass through other properties
}
}
return result;
}
[29]:
// Example API request
const apiRequest = {
query: {
page: 2,
limit: 20,
sort: 'desc',
filter: 'published'
},
body: {
title: 'Understanding Joi Validation',
content: 'Joi is a powerful schema validation library for JavaScript...',
tags: ['javascript', 'validation', 'nodejs'],
published: true,
authorId: '507f1f77bcf86cd799439011'
},
params: {
id: '507f1f77bcf86cd799439011'
},
headers: {
'content-type': 'application/json',
'authorization': 'Bearer token123'
}
};
[30]:
// Validate the API request
const validatedRequest = validateRequest(apiRequest);
console.log("Validated API request:\n");
console.log(validatedRequest, "\n");
Validated API request:
{
  query: { page: 2, limit: 20, sort: 'desc', filter: 'published' },
  body: {
    title: 'Understanding Joi Validation',
    content: 'Joi is a powerful schema validation library for JavaScript...',
    tags: [ 'javascript', 'validation', 'nodejs' ],
    published: true,
    authorId: '507f1f77bcf86cd799439011'
  },
  params: { id: '507f1f77bcf86cd799439011' },
  headers: {
    'content-type': 'application/json',
    authorization: 'Bearer token123'
  }
} 
[31]:
// Example invalid API request
const invalidApiRequest = {
query: {
page: 0, // Invalid: below minimum
limit: 200, // Invalid: above maximum
sort: 'invalid' // Invalid: not in allowed values
},
body: {
title: 'Hi', // Invalid: too short
// Missing required content field
tags: ['too', 'many', 'tags', 'in', 'this', 'array'], // Invalid: too many items
published: 'yes' // Invalid: not a boolean
// Missing required authorId field
},
params: {
id: 'not-a-valid-id' // Invalid: doesn't match pattern
}
};
[32]:
// Validate the invalid API request
const invalidValidatedRequest = validateRequest(invalidApiRequest);
console.log("Invalid API request validation result:\n");
console.log(invalidValidatedRequest.error ? invalidValidatedRequest.error.details : invalidValidatedRequest, "\n");
Invalid API request validation result:
[
  {
    message: '"page" must be greater than or equal to 1',
    path: [ 'page' ],
    type: 'number.min',
    context: { limit: 1, value: 0, label: 'page', key: 'page' }
  },
  {
    message: '"limit" must be less than or equal to 100',
    path: [ 'limit' ],
    type: 'number.max',
    context: { limit: 100, value: 200, label: 'limit', key: 'limit' }
  },
  {
    message: '"sort" must be one of [asc, desc]',
    path: [ 'sort' ],
    type: 'any.only',
    context: { valids: [Array], label: 'sort', value: 'invalid', key: 'sort' }
  }
] 
[M]:

7. Environment Configuration Validation

Joi is great for validating environment variables and configuration objects.

[33]:
// Environment configuration validation schema
const envSchema = Joi.object({
NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
PORT: Joi.number().port().default(3000),
DATABASE_URL: Joi.string().uri().required(),
API_KEY: Joi.string().required(),
LOG_LEVEL: Joi.string().valid('error', 'warn', 'info', 'debug').default('info'),
ENABLE_CACHE: Joi.boolean().default(true),
CACHE_TTL: Joi.number().integer().min(0).default(300),
CORS_ORIGINS: Joi.alternatives().try(
Joi.string().valid('*'),
Joi.array().items(Joi.string().domain())
).default('*')
}).unknown();
[34]:
// Sample environment variables
const env = {
NODE_ENV: 'production',
PORT: 8080,
DATABASE_URL: 'mongodb://localhost:27017/myapp',
API_KEY: 'secret-api-key-123',
LOG_LEVEL: 'warn',
ENABLE_CACHE: 'true', // String instead of boolean
CORS_ORIGINS: ['example.com', 'api.example.com']
// Missing CACHE_TTL, will use default
};
[35]:
// Validate and convert environment variables
const { error, value: validatedEnv } = envSchema.validate(env, {
convert: true, // Convert string values to appropriate types
stripUnknown: false // Keep unknown environment variables
});

if (error) {
console.log("Environment validation error:\n");
console.log(error.details, "\n");
} else {
console.log("Validated environment configuration:\n");
console.log(validatedEnv, "\n");
// Notice how ENABLE_CACHE was converted from string 'true' to boolean true
console.log("ENABLE_CACHE type:", typeof validatedEnv.ENABLE_CACHE, "\n");
// And CACHE_TTL was set to its default value
console.log("CACHE_TTL (default):", validatedEnv.CACHE_TTL, "\n");
}
Validated environment configuration:
{
  NODE_ENV: 'production',
  PORT: 8080,
  DATABASE_URL: 'mongodb://localhost:27017/myapp',
  API_KEY: 'secret-api-key-123',
  LOG_LEVEL: 'warn',
  ENABLE_CACHE: true,
  CORS_ORIGINS: [ 'example.com', 'api.example.com' ],
  CACHE_TTL: 300
} 
ENABLE_CACHE type: boolean 
CACHE_TTL (default): 300 
[M]:

8. Custom Validation Functions

Joi allows you to create custom validation functions for complex validation logic.

[36]:
// Custom validation function schema
const orderSchema = Joi.object({
customerId: Joi.string().required(),
items: Joi.array().items(
Joi.object({
productId: Joi.string().required(),
quantity: Joi.number().integer().min(1).required(),
price: Joi.number().positive().precision(2).required()
})
).min(1).required(),
total: Joi.number().positive().precision(2).required()
});
[37]:
// Add custom validation to verify that total matches sum of item prices
const orderSchemaWithCustomValidation = orderSchema.custom((value, helpers) => {
// Calculate the expected total
const calculatedTotal = value.items.reduce((sum, item) => {
return sum + (item.quantity * item.price);
}, 0);
// Round to 2 decimal places to avoid floating point issues
const roundedCalculatedTotal = Math.round(calculatedTotal * 100) / 100;
const roundedTotal = Math.round(value.total * 100) / 100;
// Check if the totals match
if (roundedCalculatedTotal !== roundedTotal) {
return helpers.error('order.invalidTotal', {
expected: roundedCalculatedTotal,
provided: roundedTotal
});
}
return value;
}, 'validate order total');
[38]:
// Add custom error message for the total validation
Joi.defaults((schema) => {
return schema.error((errors) => {
errors.forEach(err => {
if (err.code === 'order.invalidTotal') {
err.message = `Order total (${err.local.provided}) does not match the sum of item prices (${err.local.expected})`;
}
});
return errors;
});
});
[39]:
// Valid order data
const validOrder = {
customerId: 'cust123',
items: [
{ productId: 'prod1', quantity: 2, price: 10.99 },
{ productId: 'prod2', quantity: 1, price: 24.99 }
],
total: 46.97 // 2 * 10.99 + 1 * 24.99 = 46.97
};

const validTotalResult = orderSchemaWithCustomValidation.validate(validOrder);
console.log("Valid order validation result:\n");
console.log(validTotalResult, "\n");
Valid order validation result:
{
  value: {
    customerId: 'cust123',
    items: [ [Object], [Object] ],
    total: 46.97
  }
} 
[40]:
// Invalid order data (incorrect total)
const invalidOrder = {
customerId: 'cust123',
items: [
{ productId: 'prod1', quantity: 2, price: 10.99 },
{ productId: 'prod2', quantity: 1, price: 24.99 }
],
total: 50.00 // Incorrect total
};

const invalidTotalResult = orderSchemaWithCustomValidation.validate(invalidOrder);
console.log("Invalid order validation result:\n");
console.log(invalidTotalResult, "\n");
Invalid order validation result:
{
  value: { customerId: 'cust123', items: [ [Object], [Object] ], total: 50 },
  error: [Error [ValidationError]: Error code "order.invalidTotal" is not defined, your custom type is missing the correct messages definition] {
    _original: { customerId: 'cust123', items: [Array], total: 50 },
    details: [ [Object] ]
  }
} 
[M]:

9. Performance Considerations

Let's look at some performance considerations when using Joi.

[41]:
// Define a simple schema for performance testing
const simpleSchema = Joi.object({
id: Joi.string().required(),
name: Joi.string().required(),
email: Joi.string().email().required()
});
[42]:
// Define a complex schema for performance testing
const complexSchema = Joi.object({
id: Joi.string().required(),
name: Joi.string().min(3).max(50).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(18).max(100).required(),
address: Joi.object({
street: Joi.string().required(),
city: Joi.string().required(),
state: Joi.string().length(2).required(),
zipCode: Joi.string().pattern(/^\d{5}(-\d{4})?$/).required()
}).required(),
tags: Joi.array().items(Joi.string()).min(1).max(10).required(),
settings: Joi.object().pattern(Joi.string(), Joi.any()).required()
});
[43]:
// Create test data for simple schema
const simpleData = {
id: '123',
name: 'John Doe',
email: 'john@example.com'
};
[44]:
// Create test data for complex schema
const complexData = {
id: '123',
name: 'John Doe',
email: 'john@example.com',
age: 30,
address: {
street: '123 Main St',
city: 'Anytown',
state: 'CA',
zipCode: '12345'
},
tags: ['tag1', 'tag2', 'tag3'],
settings: {
theme: 'dark',
notifications: true,
language: 'en'
}
};
[45]:
// Measure performance for simple schema
console.log("Performance test:\n");

console.time('Simple schema validation');
for (let i = 0; i < 1000; i++) {
simpleSchema.validate(simpleData);
}
console.timeEnd('Simple schema validation');
console.log("\n");
Error during execution: console.time is not a functionPerformance test:
[46]:
// Measure performance for complex schema
console.time('Complex schema validation');
for (let i = 0; i < 1000; i++) {
complexSchema.validate(complexData);
}
console.timeEnd('Complex schema validation');
console.log("\n");
Error during execution: console.time is not a function
[47]:
// Measure performance for compiled schema
const compiledSimpleSchema = Joi.compile(simpleSchema);
console.time('Compiled simple schema validation');
for (let i = 0; i < 1000; i++) {
compiledSimpleSchema.validate(simpleData);
}
console.timeEnd('Compiled simple schema validation');
console.log("\n");
Error during execution: console.time is not a function
[M]:

10. Practical Tips for Using Joi

Here are some practical tips for using Joi effectively in your projects.

[48]:
// Tip 1: Reuse schema components
// Define common schema components that can be reused
const schemas = {
id: Joi.string().pattern(/^[0-9a-fA-F]{24}$/),
email: Joi.string().email().required(),
password: Joi.string().min(8).pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/).required(),
phone: Joi.string().pattern(/^\+?[1-9]\d{1,14}$/)
};

// Use the components in different schemas
const userSchemaReusable = Joi.object({
id: schemas.id,
email: schemas.email,
password: schemas.password,
phone: schemas.phone.optional()
});

console.log("Reusable schema components:\n");
console.log(userSchemaReusable.describe(), "\n");
Reusable schema components:
{
  type: 'object',
  keys: {
    id: { type: 'string', rules: [Array] },
    email: { type: 'string', flags: [Object], rules: [Array] },
    password: { type: 'string', flags: [Object], rules: [Array] },
    phone: { type: 'string', flags: [Object], rules: [Array] }
  }
} 
[49]:
// Tip 2: Use describe() to inspect schemas
const schemaDescription = userSchema.describe();
console.log("Schema description:\n");
console.log(JSON.stringify(schemaDescription, null, 2), "\n");
Schema description:
{
  "type": "object",
  "keys": {
    "username": {
      "type": "string",
      "flags": {
        "presence": "required"
      },
      "rules": [
        {
          "name": "alphanum"
        },
        {
          "name": "min",
          "args": {
            "limit": 3
          }
        },
        {
          "name": "max",
          "args": {
            "limit": 30
          }
        }
      ]
    },
    "email": {
      "type": "string",
      "flags": {
        "presence": "required"
      },
      "rules": [
        {
          "name": "email"
        }
      ]
    },
    "birthYear": {
      "type": "number",
      "rules": [
        {
          "name": "integer"
        },
        {
          "name": "min",
          "args": {
            "limit": 1900
          }
        },
        {
          "name": "max",
          "args": {
            "limit": 2023
          }
        }
      ]
    },
    "password": {
      "type": "string",
      "rules": [
        {
          "name": "pattern",
          "args": {
            "regex": "/^[a-zA-Z0-9]{3,30}$/"
          }
        }
      ]
    }
  }
} 
[50]:
// Tip 3: Create a validation middleware for Express
function createValidationMiddleware(schema, property = 'body') {
return (req, res, next) => {
const { error, value } = schema.validate(req[property], { abortEarly: false });
if (error) {
const errorDetails = error.details.map(detail => ({
message: detail.message,
path: detail.path
}));
return res.status(400).json({
status: 'error',
message: 'Validation error',
errors: errorDetails
});
}
// Replace the request data with the validated data
req[property] = value;
next();
};
}

console.log("Express validation middleware created\n");
Express validation middleware created
[M]:

Summary

This notebook has introduced Joi, a powerful schema validation library for JavaScript. We've covered:

  1. Basic Validation: Creating schemas and validating data against them
  2. Validation Options: Customizing validation behavior with options like allowUnknown and stripUnknown
  3. Advanced Validation Rules: Using Joi's rich set of validation rules for different data types
  4. Conditional Validation: Creating schemas with conditional validation logic
  5. Complex Object Validation: Validating nested objects and arrays
  6. API Request Validation: Using Joi to validate API requests
  7. Environment Configuration Validation: Validating environment variables and configuration objects
  8. Custom Validation Functions: Creating custom validation logic
  9. Performance Considerations: Understanding performance implications
  10. Practical Tips: Best practices for using Joi effectively

Joi is a versatile tool that can help ensure data integrity in your applications. It's particularly useful for:

  • Validating user input in forms
  • Validating API requests and responses
  • Validating configuration objects
  • Ensuring data consistency in your application

For more information, check out the Joi documentation.

Sign in to save your work and access it from anywhere