javascriptnode.jstypescriptvalidationjoi

Joi validator validateAsync remove warnings


I have been using Joi library for validation user inputs like this.

const Joi = require('joi');

const schema = Joi.object({
    username: Joi.string()
        .alphanum()
        .min(3)
        .max(30)
        .required()
});

const validationResult = await schema.validateAsync({ username: 'a' }, { abortEarly: false, warnings: true });

This works fine, the problem is later in code I would want to use this validation again. But the warning messages are still in the local context.

And if I input he { username: 'ba'} the error message will still display that min length of "a" needs to be 3.

Do you know how can I use the schema object again, and strip the local warnings context.


Solution

  • Validation context in Joi can carry over between validations when reusing the same schema instance. Try these two approaches and see if it solves the problem:

    Create fresh schema clone for each validation:

    const baseSchema = Joi.object({
        username: Joi.string()
            .alphanum()
            .min(3)
            .max(30)
            .required()
    });
    
    // Use schema.clone() for each validation
    const validationResult1 = await baseSchema.clone().validateAsync({ username: 'a' });
    const validationResult2 = await baseSchema.clone().validateAsync({ username: 'ba' });
    

    Create a validation function that always uses a fresh schema:

    const validateUsername = async (data) => {
        const schema = Joi.object({
            username: Joi.string()
                .alphanum()
                .min(3)
                .max(30)
                .required()
        });
        
        return await schema.validateAsync(data, { abortEarly: false, warnings: true });
    };
    
    // Use it multiple times
    try {
        await validateUsername({ username: 'a' });
        await validateUsername({ username: 'ba' });
    } catch (err) {
        console.error(err);
    }
    

    The second approach is generally preferred because:

    I hope this helps

    UPDATED TO ADDRESS NEW ISSUE:

    You're caching the schema in a Map but still running into the same context persistence issue. The solution below allows for efficient caching while avoiding the context issue.

    const Joi = require('joi');
    
    // Create a schema cache using Map
    class ValidationSchemaCache {
        constructor() {
            this.schemaCache = new Map();
        }
    
        getSchema(schemaName) {
            if (!this.schemaCache.has(schemaName)) {
                // Define your schemas here
                const schemas = {
                    'user': Joi.object({
                        username: Joi.string()
                            .alphanum()
                            .min(3)
                            .max(30)
                            .required()
                    })
                    // Add other schemas as needed
                };
    
                const schema = schemas[schemaName];
                if (!schema) {
                    throw new Error(`Schema ${schemaName} not found`);
                }
    
                this.schemaCache.set(schemaName, schema);
            }
    
            // Return a clone of the cached schema
            return this.schemaCache.get(schemaName).clone();
        }
    
        async validate(schemaName, data, options = { abortEarly: false, warnings: true }) {
            const schema = this.getSchema(schemaName);
            return await schema.validateAsync(data, options);
        }
    }
    
    // Usage example:
    const validationCache = new ValidationSchemaCache();
    
    async function test() {
        try {
            // First validation
            const result1 = await validationCache.validate('user', { username: 'a' });
            console.log('Result 1:', result1);
        } catch (err1) {
            console.error('Error 1:', err1.details);
        }
    
        try {
            // Second validation with different data
            const result2 = await validationCache.validate('user', { username: 'ba' });
            console.log('Result 2:', result2);
        } catch (err2) {
            console.error('Error 2:', err2.details);
        }
    }
    
    test();
    

    What I’ve done here is:

    Some of the pros of using this approach:

    You may expand this further by: