Hey there! If you've been using Joi for validation, you've probably noticed that the default error messages can be a bit... technical. In this notebook, we'll explore how to customize error messages and create reusable validation patterns to make your validation more user-friendly and maintainable.
We'll cover:
Let's make those validation errors actually helpful!
$ npm install joi added 6 packages in 2s 28 packages are looking for funding run `npm fund` for details
Joi version: 17.13.3
Let's start by comparing default Joi error messages with customized ones.
Default error messages: [ { "message": "\"username\" must only contain alpha-numeric characters", "path": [ "username" ], "type": "string.alphanum", "context": { "label": "username", "value": "a!", "key": "username" } }, { "message": "\"username\" length must be at least 3 characters long", "path": [ "username" ], "type": "string.min", "context": { "limit": 3, "value": "a!", "label": "username", "key": "username" } }, { "message": "\"email\" must be a valid email", "path": [ "email" ], "type": "string.email", "context": { "value": "not-an-email", "invalids": [ "not-an-email" ], "label": "email", "key": "email" } }, { "message": "\"password\" length must be at least 8 characters long", "path": [ "password" ], "type": "string.min", "context": { "limit": 8, "value": "short", "label": "password", "key": "password" } } ]
Custom error messages: [ { "message": "Username must only contain letters and numbers", "path": [ "username" ], "type": "string.alphanum", "context": { "label": "username", "value": "a!", "key": "username" } }, { "message": "Username must be at least 3 characters long", "path": [ "username" ], "type": "string.min", "context": { "limit": 3, "value": "a!", "label": "username", "key": "username" } }, { "message": "Please enter a valid email address", "path": [ "email" ], "type": "string.email", "context": { "value": "not-an-email", "invalids": [ "not-an-email" ], "label": "email", "key": "email" } }, { "message": "Password must be at least 8 characters long", "path": [ "password" ], "type": "string.min", "context": { "limit": 8, "value": "short", "label": "password", "key": "password" } } ]
Instead of repeating the same error messages across schemas, let's create reusable message templates.
Reusable error messages: [ { "message": "\"username\" must only contain alpha-numeric characters", "path": [ "username" ], "type": "string.alphanum", "context": { "label": "username", "value": "a!", "key": "username" } }, { "message": "\"username\" length must be at least 3 characters long", "path": [ "username" ], "type": "string.min", "context": { "limit": 3, "value": "a!", "label": "username", "key": "username" } }, { "message": "\"email\" must be a valid email", "path": [ "email" ], "type": "string.email", "context": { "value": "not-an-email", "invalids": [ "not-an-email" ], "label": "email", "key": "email" } }, { "message": "\"password\" length must be at least 8 characters long", "path": [ "password" ], "type": "string.min", "context": { "limit": 8, "value": "short", "label": "password", "key": "password" } } ]
Sometimes you need different error messages for the same validation rule in different contexts.
Signup validation with context-specific messages: [ { message: 'For security, your password must be at least 8 characters', path: [ 'password' ], type: 'string.min', context: { limit: 8, value: 'short', encoding: undefined, label: 'password', key: 'password' } }, { message: 'Passwords do not match', path: [ 'passwordConfirm' ], type: 'any.only', context: { valids: [Array], label: 'passwordConfirm', value: 'different', key: 'passwordConfirm' } } ]
Login validation with context-specific messages: [ { message: 'Please enter your password to log in', path: [ 'password' ], type: 'any.required', context: { label: 'password', key: 'password' } } ]
Let's create some custom validation patterns with helpful error messages.
Password strength validation: [ { message: 'Password must contain at least 8 characters, including uppercase, lowercase, number, and special character', path: [ 'password' ], type: 'string.pattern.base', context: { name: undefined, regex: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, value: 'simple', label: 'password', key: 'password' } } ]
Phone format validation: [ { message: 'Phone number must be in format XXX-XXX-XXXX', path: [ 'phone' ], type: 'string.pattern.base', context: { name: undefined, regex: /^\d{3}-\d{3}-\d{4}$/, value: '1234567890', label: 'phone', key: 'phone' } } ]
Sometimes you need complex validation logic that goes beyond Joi's built-in validators.
Expired card validation: [ { message: 'Your card has expired', path: [], type: 'payment.expired', context: { month: 1, year: 2025, label: 'value', value: [Object] } } ]
Invalid card number validation: [ { message: 'Invalid card number', path: [], type: 'payment.invalidCardNumber', context: { label: 'value', value: [Object] } } ]
If your application supports multiple languages, you'll want to localize error messages.
English error messages: [ { message: '"name" must be at least 2 characters', path: [ 'name' ], type: 'string.min', context: { limit: 2, value: 'A', encoding: undefined, label: 'name', key: 'name' } }, { message: '"email" is required', path: [ 'email' ], type: 'any.required', context: { label: 'email', key: 'email' } } ]
Spanish error messages: [ { message: '"name" debe tener al menos 2 caracteres', path: [ 'name' ], type: 'string.min', context: { limit: 2, value: 'A', encoding: undefined, label: 'name', key: 'name' } }, { message: '"email" es obligatorio', path: [ 'email' ], type: 'any.required', context: { label: 'email', key: 'email' } } ]
French error messages: [ { message: '"name" doit comporter au moins 2 caractères', path: [ 'name' ], type: 'string.min', context: { limit: 2, value: 'A', encoding: undefined, label: 'name', key: 'name' } }, { message: '"email" est requis', path: [ 'email' ], type: 'any.required', context: { label: 'email', key: 'email' } } ]
Finally, let's create a helper function to format error responses for API endpoints.
Formatted API validation errors: { "status": "error", "message": "Validation failed", "errors": [ { "field": "username", "messages": [ "Username must only contain letters and numbers", "Username must be at least 3 characters long" ] }, { "field": "email", "messages": [ "Please enter a valid email address" ] }, { "field": "age", "messages": [ "You must be at least 18 years old" ] } ] }
We've covered a lot of ground on customizing error messages and validation patterns in Joi:
With these techniques, you can make your validation not just functional but actually helpful to your users. Good validation isn't just about rejecting bad data—it's about guiding users to provide the right data.