26
votes

I want to validate my Express routes before calling the controller logic. I use joi and created a validator which is able to validate the Request object against the schema object

import { Request, Response, NextFunction } from 'express';
import joi, { SchemaLike, ValidationError, ValidationResult } from '@hapi/joi';
import { injectable } from 'inversify';

@injectable()
export abstract class RequestValidator {
    protected validateRequest = (validationSchema: SchemaLike, request: Request, response: Response, next: NextFunction): void => {
        const validationResult: ValidationResult<Request> = joi.validate(request, validationSchema, {
            abortEarly: false
        });

        const { error }: { error: ValidationError } = validationResult;

        if (error) {
            response.status(400).json({
                message: 'The request validation failed.',
                details: error.details
            });
        } else {
            next();
        }
    }
}

Next I created a deriving class which creates the validationSchema and calls the validateRequest method. For the sake of simplicity I will show the "deleteUserById" validation

import { Request, Response, NextFunction } from 'express';
import joi, { SchemaLike } from '@hapi/joi';
import { injectable } from 'inversify';

import { RequestValidator } from './RequestValidator';

@injectable()
export class UserRequestValidator extends RequestValidator {
    public deleteUserByIdValidation = async (request: Request, response: Response, next: NextFunction): Promise<void> => {
        const validationSchema: SchemaLike = joi.object().keys({
            params: joi.object().keys({
                id: joi.number().required(),
            })
        });

        this.validateRequest(validationSchema, request, response, next);
    }
}

Important note: I create the SchemaLike that way because some routes might have params, body, query which need to get validated in one run.

When calling the Route

DELETE /users/1

the validation always fails. I get this error

UnhandledPromiseRejectionWarning: TypeError: joi_1.default.validate is not a function

The error occurs with every validation, whether called correctly or not. Does someone know how to fix it?

16

16 Answers

11
votes

Hit the same problem when using express-joi-validation.

If it's ok for you to use version 15, downgrade Joi will make it.

npm uninstall --save @hapi/joi
npm install --save @hapi/[email protected]
23
votes

You fix it by changing joi.validate(request, validationSchema to validationSchema.validate(request As joi.validate() is no longer supported in v16. It is clearly documented in the API docs and release notes.

16
votes

Update version of joi doesn't work with Joi.validate(req.body,schema); No need to use the object separately. Use this as following. It Worked for me smoothly. Do let me know if I am wrong at anything:

const Joi = require('@hapi/joi');

const schema = Joi.object({
    name:Joi.string().min(3).required(),
    email:Joi.string().min(4).required().email(),
    password:Joi.string().min(6).required()
});





router.post('/register', async (req,res) => {


    //updated joi
    
   // This is a shorter version
    const { error } = schema.validate(req.body);

    // Error in response

    res.send(error.details[0].message);


    // WORKING WITH DEPRECATED VERSION
    // const Validation = Joi.validate(req.body,schema);
    // res.send(Validation);
6
votes

I experienced joi.validate() is not a function also. I checked their documentation and got a fix for it.

    const Joi = require('@hapi/joi');

const schema = Joi.object({
    name:Joi.string().min(6).required(),
    email:Joi.string().min(6).required().email(),
    password:Joi.string().min(6).required()
});

router.post('/register',  (req, res) => {

    // VALIDATE BEFORE SAVING A USER 
    const Validation = schema.validate(req.body);
    res.send(Validation);

    
})

This works as expected and gives no further error.

6
votes

You can fix it by changing Joi.validate(request, validationSchema) to validationSchema.validate(request) As Joi.validate() is no longer supported in v16.

For new version

const schema = Joi.object({ name: Joi.string() .min(6) .required(),
email: Joi.string() .min(6) .required() .email(),
password: Joi.string() .min(6) .required() });

const validation = schema.validate(req.body);
4
votes

Instead of downgrading the Joi version it's better to quickly have a look at API of the newest version and check the correct way of using it.

Here is the link to the latest Joi API at the moment (16.1.7), where you can see an example of using validate.

Also to make sure you know what has been changed in next version of a lib that you use, it is good to have a look at the Release notes, here is a link to a Joi version 16 Release notes where you can see all the changes/new features, and FYI you can see info about validate method:

Remove Joi.validate() and Joi.describe() (call direct on schema instead) (#1941)

2
votes

For the new version

const schema = Joi.object({ name: Joi.string() .min(6) .required(),
email: Joi.string() .min(6) .required() .email(),
password: Joi.string() .min(6) .required() });

const validation = schema.validate(req.body);
res.send(validation);
1
votes

Here are some fixed with snippet codes :)

Install below npm package.

npm install --save @hapi/[email protected]

req-validator.js file

const Joi = require("@hapi/joi");

const registerValidation = data => {

const schema = {
    name : Joi.string().min(4).required(),
    email: Joi.string().min(4).required().email(),
    password: Joi.string().min(4).required()
};
return Joi.validate(data,schema);

}
module.exports.registerValidation = registerValidation;

finally, calling the validation method in the controller.

const router = require("express").Router();
const {registerValidation}  = require("./req-validator")

router.post("/register",async(req,res)=> {

const {error} =  registerValidation(req.body);
if(error){
    return res.status(400).send(error.details[0].message);
}
 // logic to save in DB....
}
1
votes

Simply You can check whether you have @hapi/joi installed and required in your file

and for validation use This approach.

const schema= Joi.object().keys({

    name: Joi.string().min(6).required(),
    email: Joi.string().min(6).email(),
    password: Joi.string().min(6).required()
});

 const validation = schema.validate(req.body);
    if(validation.error){
        res.status(400).send(validation.error.details[0].message);
        return ;
    }

I think This is gonna work

0
votes

Create your validate function like this

const validateUser = function (user) {
  const schema = Joi.object({
    username: Joi.string().min(5).max(50),
    email: Joi.string().required().email().min(5).max(255),
    password: Joi.string().required().min(5).max(1024),
  });

  return schema.validate(user);
};

You will get the error by

const { error } = validateUser({ email: 'admin' });

And the error message by

const message = error.details[0].message;
0
votes

// Get the instance on schema defination

function validate(model, object, scope) {
  const schema = getSchema(model, scope);
  return schema.validate(object);
}

// actual middleware factory to validate

module.exports = function ValidationMiddleware(model, scope) {
  return (req, res, next) => {
    const validationResult = validate(model, req.body, scope);
    if (validationResult.error) {
      throw new Error(validationResult.error.details[0].message);
    } else {
      next();
    }
  };
};
0
votes

use this

const schema = Joi.object({
    name: Joi.string().min(6).required(),
    email: Joi.string().min(6).email(),
    password: Joi.string().min(6).required()
})

insted of const schema = {...}

and Validate through this:

const validate = schema.validate(req.body)

instead of Joi.validate(req.body,schema)

0
votes

Here is something straight out of a project I'm working on, using Joi 17.3.0:

PDFCreatorController.exportAsPDF = (req, res) => {
  const data = req.body
  const schema = Joi.object().keys({
    svgDataToExport: Joi.string().required()
  })
  const isValidPromise = new Promise((resolve) => {
    const validation = schema.validate(req.body)
    if (validation.error) {
      res.status(422).json({
        status: 'error',
        message: error,
        data: data
      })
    } else {
      resolve(data)
    }
  })
  isValidPromise.then(inputDataFromUser => {
    try {
      const exportAsPDF = PDFCreatorModel.exportAsPDF(inputDataFromUser)
      exportAsPDF.then(data => {
        res.json(data)
      })
    } catch (error) {
      console.error(error)
      return res.status(500).json(error)
    }
  })
}
0
votes

Due to Updated version of JOI Package we are all facing this error but here is the solution of that. In Node.js when we facing this error we can solve it applying below solution.

// Example : Here we try to validate user's data
// User's POST Api 

const schema = Joi.object({
    name: Joi.string().min(1).required(),
    email:  Joi.string().required().email(),
    password: Joi.string().min(8).max(12).required(),
    isEmailVerified: Joi.boolean().required(),
    forgotPasswordToken: Joi.string().required(),
    isDeleted: Joi.boolean().required()
});
try {
    const value = await schema.validateAsync(req.body);
}
catch (err) {
    return res.status(400).send(err);
 }
0
votes
Following works with the latest version ("@hapi/joi": "^17.1.1"):

    const Joi = require("@hapi/joi");

    const schema = Joi.object({
      name: Joi.string().min(6).required(),
      email: Joi.string().min(6).required().email(),
      password: Joi.string().min(6).required(),
    });
    
  router.post("/register", async (req, res) => {
  const { error } = schema.validate(req.body);

  if (error) {
    res.json({ ErrorMessage: error.details[0].message });
  } else {
    res.json({ message: "valid data" });
  }

});
0
votes

Lower down the version of joi. to 13.0.1 it will work. in terminal npm i [email protected]. 2.after install check. dependencies in package. Json file.