npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

joi-schema-validator

v3.1.0

Published

A world-class Joi validation error formatter for structured, user-friendly error handling.

Downloads

26

Readme

joi-schema-validator

npm license build downloads

joi-schema-validator is a structured and user-friendly error handler for Joi validation in Node.js applications. It simplifies error messages and supports synchronous, asynchronous, and middleware-based validation.


Key Features

  • Synchronous Validation (validate): Validate input and return structured error messages.
  • Asynchronous Validation (validateAsync): Support for database lookups or external API validation.
  • Express Middleware (validateMiddleware): Automatically validate incoming requests.
  • Custom Error Messages: Define global or field-specific messages.
  • Enhanced Type Safety: Strongly typed validation results.
  • Supports Nested Objects: Handles deeply nested field errors.
  • Enterprise-Ready: Lightweight and high-performance.

Installation

Install via npm or yarn:

npm install joi-schema-validator

or

yarn add joi-schema-validator

Usage

Import the required functions:

import {
	validate,
	validateAsync,
	validateMiddleware
} from 'joi-schema-validator';

1. Synchronous Validation (validate)

Example:

import Joi from 'joi';
import { validate } from 'joi-schema-validator';

const schema = Joi.object({
	name: Joi.string().min(3).required().label('Name'),
	age: Joi.number().min(18).required().label('Age'),
	email: Joi.string().email().required().label('Email'),
	address: Joi.string().optional().label('Address')
});

const customMessages = {
	'string.min': '{#label} must have at least {#limit} characters.',
	'name.string.min':
		'Name should be at least {#limit} characters long. Please provide a valid name.'
};

const result = validate(
	schema,
	{ name: 'Jo', age: 16, email: 'invalid', address: '' },
	customMessages
);

console.log(result);

Output Response

{
	"value": {
		"name": "Jo",
		"age": 16,
		"email": "invalid",
		"address": ""
	},
	"error": [
		{
			"field": "name",
			"type": "string.min",
			"message": "Name should be at least 3 characters long. Please provide a valid name."
		},
		{
			"field": "age",
			"type": "number.min",
			"message": "Age must be greater than or equal to 18."
		},
		{
			"field": "email",
			"type": "string.email",
			"message": "Email must be a valid email address."
		}
	]
}

2. Asynchronous Validation (validateAsync)

Example:

import Joi from 'joi';
import { validateAsync } from 'joi-schema-validator';

const schema = Joi.object({
	username: Joi.string()
		.min(3)
		.required()
		.external(async (value) => {
			const existingUsers = ['existingUser', 'user123'];
			if (existingUsers.includes(value)) {
				throw new Error('Username already exists.');
			}
			return value;
		}),
	email: Joi.string().email().required()
});

async function runAsyncValidation() {
	const result = await validateAsync(schema, {
		username: 'existingUser',
		email: '[email protected]'
	});
	console.log(result);
}

runAsyncValidation();

Output Response

{
	"value": {
		"username": "ex",
		"email": "[email protected]"
	},
	"error": [
		{
			"field": "username",
			"type": "external",
			"message": "Username already exists."
		}
	]
}

3. Express Middleware (validateMiddleware)

Example:

import express from 'express';
import Joi from 'joi';
import { validateMiddleware } from 'joi-schema-validator';

const app = express();
app.use(express.json());

const userSchema = Joi.object({
	name: Joi.string().min(3).required().label('Name'),
	email: Joi.string().email().required().label('Email')
});

app.post('/user', validateMiddleware(userSchema), (req, res) => {
	res.json({ success: true, data: req.body });
});

app.listen(3000, () => console.log('Server running on port 3000'));

Example Request

POST /user
Content-Type: application/json

{
  "name": "Jo",
  "email": "invalidEmail"
}

Output Response (HTTP 400 Bad Request)

{
	"errors": [
		{
			"field": "name",
			"type": "string.min",
			"message": "Name must have at least 3 characters."
		},
		{
			"field": "email",
			"type": "string.email",
			"message": "Email must be a valid email address."
		}
	]
}

4. Custom Error Messages

joi-schema-validator allows you to provide your own error message templates that override (or extend) the default messages. You can specify:

  1. Global Overrides:
    These custom messages apply to every validation error of a given type. For example, setting a custom message for 'string.min' will change the message for any string that fails the minimum length validation.

  2. Field-Level Overrides:
    These are specific to a particular field. For instance, you can provide a custom message for the 'name' field when it fails a minimum length check using the key 'name.string.min'.

  3. Fallback Behavior:
    If no custom message is provided for a particular error, the default message (from defaultErrorMessages) is used. If neither is available, the error message defaults to "<field> is invalid.".

How to Define Custom Error Messages

Custom messages are passed as an ErrorMessages object. The keys in this object are either:

  • Global error type keys, e.g.:
    {
      'string.min': '{#label} must have at least {#limit} characters.'
    }
  • Field-specific keys, e.g.:
    {
      'name.string.min': 'Name should be at least {#limit} characters long. Please provide a valid name.'
    }

Placeholders like {#label} and {#limit} will be replaced with values from the Joi validation context (e.g., the field label or the limit specified in the schema).

Usage in Different APIs

1. Synchronous Validation

When using the validate function, pass the custom messages as the third parameter:

import Joi from 'joi';
import { validate } from 'joi-schema-validator';

const schema = Joi.object({
	name: Joi.string().min(3).required().label('Name'),
	age: Joi.number().min(18).required().label('Age'),
	email: Joi.string().email().required().label('Email')
});

// Define custom error messages
const customMessages = {
	// Global override for all string.min errors
	'string.min': '{#label} must have at least {#limit} characters.',
	// Field-specific override for the "name" field
	'name.string.min':
		'Name should be at least {#limit} characters long. Please provide a valid name.'
};

const result = validate(
	schema,
	{ name: 'Jo', age: 16, email: 'invalid' },
	customMessages
);
if (result.error) {
	console.log('Formatted Errors:', result.formattedErrors);
} else {
	console.log('Validated Data:', result.value);
}

Output Response:

  • For name failing the min check, you’ll see the custom field-specific message.
  • For other errors (like age and email), you’ll see the global custom message or default messages if no override exists.
{
	"value": {
		"name": "Jo",
		"age": 16,
		"email": "invalid",
		"address": ""
	},
	"error": [
		{
			"field": "name",
			"type": "string.min",
			"message": "Name should be at least 3 characters long. Please provide a valid name."
		},
		{
			"field": "age",
			"type": "number.min",
			"message": "Age must be greater than or equal to 18."
		},
		{
			"field": "email",
			"type": "string.email",
			"message": "Email must be a valid email address."
		}
	]
}

2. Asynchronous Validation

The usage is similar when using validateAsync:

import Joi from 'joi';
import { validateAsync } from 'joi-schema-validator';

const schema = Joi.object({
	username: Joi.string()
		.min(3)
		.required()
		.external(async (value) => {
			const existingUsers = ['existingUser'];
			if (existingUsers.includes(value)) {
				throw new Error('Username already exists.');
			}
			return value;
		}),
	email: Joi.string().email().required()
});

const customMessages = {
	'string.min': '{#label} must have at least {#limit} characters.',
	'username.string.min':
		'Username should be at least {#limit} characters long. Please choose a longer username.'
};

async function runAsyncValidation() {
	const result = await validateAsync(
		schema,
		{ username: 'ex', email: '[email protected]' },
		customMessages
	);
	if (result.error) {
		console.log('Async Formatted Errors:', result.formattedErrors);
	} else {
		console.log('Async Validated Data:', result.value);
	}
}

runAsyncValidation();

Output Response:

  • If the username fails the min check, the field-specific message is used.
  • Other errors use the global message or fallback to the default.
{
	"value": {
		"username": "ex",
		"email": "[email protected]"
	},
	"error": [
		{
			"field": "username",
			"type": "external",
			"message": "Username already exists."
		}
	]
}

3. Express Middleware for Validation

When using the Express middleware, simply pass the custom messages to the middleware function:

import express from 'express';
import Joi from 'joi';
import { validateMiddleware } from 'joi-schema-validator';

const app = express();
app.use(express.json());

const userSchema = Joi.object({
	name: Joi.string().min(3).required().label('Name'),
	email: Joi.string().email().required().label('Email')
});

const customMessages = {
	'string.min': '{#label} must have at least {#limit} characters.',
	'name.string.min': 'Name should be at least {#limit} characters long.'
};

app.post(
	'/user',
	validateMiddleware(userSchema, customMessages),
	(req, res) => {
		res.json({ success: true, data: req.body });
	}
);

app.listen(3000, () => console.log('Server running on port 3000'));

Output Response if Validation Fails:
If a POST request to /user contains invalid data, the middleware will respond with aN e JSOrror payload like:

{
	"success": false,
	"errors": [
		{
			"field": "name",
			"type": "string.min",
			"message": "Name should be at least 3 characters long."
		},
		{
			"field": "email",
			"type": "string.email",
			"message": "Email must be a valid email address."
		}
	]
}

API Reference

1. validate(schema, input, messages?, options?)

  • Validates input synchronously against a Joi schema.
  • Returns a ValidationResult<T>.
  • Example:
    const result = validate(schema, input, customMessages, {
    	abortEarly: false
    });

2. validateAsync(schema, input, messages?, options?)

  • Validates input asynchronously, useful for database or external API checks.
  • Returns a Promise<ValidationResult<T>>.
  • Example:
    const result = await validateAsync(schema, input);

3. validateMiddleware(schema, messages?)

  • Express middleware that validates req.body.
  • If validation fails, returns a 400 response.
  • Example:
    app.post('/route', validateMiddleware(schema), handler);

Type Definitions

export interface ValidationErrorItemFormatted {
	field: string;
	type: string;
	message: string;
}

export interface ErrorMessages {
	[key: string]: string;
}

export interface ValidationResult<T> {
	value: T;
	error: ValidationError | null;
	formattedErrors?: ValidationErrorItemFormatted[];
}

License

This project is licensed under the MIT License.

Issues & Support

For issues, report them here.


Author

Developed by Pavan Dulam.