UNPKG

joi-schema-validator

Version:

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

517 lines (408 loc) 12 kB
# **joi-schema-validator** ![npm](https://img.shields.io/npm/v/joi-schema-validator) ![license](https://img.shields.io/npm/l/joi-schema-validator) ![build](https://img.shields.io/github/actions/workflow/status/pavan-dulam/joi-schema-validator/publish.yml) ![downloads](https://img.shields.io/npm/d18m/joi-schema-validator) **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: ```sh npm install joi-schema-validator ``` or ```sh yarn add joi-schema-validator ``` --- ## **Usage** Import the required functions: ```typescript import { validate, validateAsync, validateMiddleware } from 'joi-schema-validator'; ``` --- # **1. Synchronous Validation (`validate`)** ### **Example:** ```typescript 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** ```json { "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:** ```typescript 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: 'test@example.com' }); console.log(result); } runAsyncValidation(); ``` ### **Output Response** ```json { "value": { "username": "ex", "email": "test@example.com" }, "error": [ { "field": "username", "type": "external", "message": "Username already exists." } ] } ``` --- # **3. Express Middleware (`validateMiddleware`)** ### **Example:** ```typescript 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** ```json POST /user Content-Type: application/json { "name": "Jo", "email": "invalidEmail" } ``` ### **Output Response (HTTP 400 Bad Request)** ```json { "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.: ```js { 'string.min': '{#label} must have at least {#limit} characters.' } ``` - **Field-specific keys**, e.g.: ```js { '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: ```typescript 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. ```json { "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`: ```typescript 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: 'test@example.com' }, 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. ```json { "value": { "username": "ex", "email": "test@example.com" }, "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: ```typescript 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: ```json { "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:** ```typescript 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:** ```typescript const result = await validateAsync(schema, input); ``` ### **3. `validateMiddleware(schema, messages?)`** - **Express middleware** that validates `req.body`. - **If validation fails, returns a `400` response.** - **Example:** ```typescript app.post('/route', validateMiddleware(schema), handler); ``` --- ## **Type Definitions** ```typescript 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](https://github.com/pavan-dulam/joi-schema-validator/issues). --- ## **Author** Developed by **[Pavan Dulam](mailto:pavandulam16@gmail.com)**.