Validate uniqueness in node.js with Joi

Issue

How to check if the incoming request value is unique or not with the help of Joi package.

exports.createUser = {
  body: Joi.object().keys({
    email: Joi.string().required().email(),
    password: Joi.string().required().custom(password),
    phoneNumber: Joi.string().min(10).required(),
  }),
};

Here I want to check if email is unique or not. I am aware that I can do this in the mongoose(Schema) but I want to do it with joi package.

The API endpoint:

router
  .route("/register")
  .post(validate(userValidation.createUser), User.register);

The register controller:

exports.register = catchAsync(async (req, res) => {
  try {
    var isValidated = await userService.validateInDatabase(req);
    if (!isValidated)
      return res
        .status(409)
        .json({ error: "Phone number or email is already registered" });

    var user = await userService.create(req.body);
    var token = await sendVerification(user);
    return res.status(201).json({ user, token });
  } catch (error) {
    return res.status(400).json({ message: error });
  }
});

I have a function called validateInDatabase but I do not want to use it.

The userService.create service function:

exports.create = async (user) => {
  const hashedPassword = passwordHash.generate(user.password);

  let new_user = new User({
    phoneNumber: user.phoneNumber,
    email: user.email,
    password: hashedPassword,
  });

  const payload = {
    id: new_user._id,
  };
  let JWToken = jwt.sign(payload, keys.JWToken, { expiresIn: 31556926 });
  const userData = await new_user.save();
  return { userData, JWToken };
};

The validate function:

const validate = (schema) => (req, res, next) => {
  const validSchema = pick(schema, ["params", "query", "body"]);
  const object = pick(req, Object.keys(validSchema));
  const { value, error } = Joi.compile(validSchema)
    .prefs({ errors: { label: "key" }, abortEarly: false })
    .validate(object);

  if (error) {
    const errorMessage = error.details
      .map((details) => details.message)
      .join(", ");
    return res.status(400).json({ error: errorMessage });
  }
  Object.assign(req, value);
  return next();
};

Solution

You can use custom (for sync) or external validation(for async) and check the email into the database.

Something like this:

exports.createUser = {
  body: Joi.object().keys({
    email: Joi.string().required().email().external(async (email) => {
        // You have to create `checkEmailInUse` funciton somewhere in your code and call it here
        const isEmailInUse = await checkEmailInUse(email);
        if(isEmailInUse) {
            throw new Error('email in use');
        }

        return email;
    }),
    password: Joi.string().required().custom(password),
    phoneNumber: Joi.string().min(10).required(),
  }),
};

and here use validateAsync instead of validate

const validate = (schema) => (req, res, next) => {
  const validSchema = pick(schema, ["params", "query", "body"]);
  const object = pick(req, Object.keys(validSchema));
  try {
      const value = await Joi.compile(validSchema)
        .prefs({ errors: { label: "key" }, abortEarly: false })
        .validateAsync(object);
        
      Object.assign(req, value);
      return next();
  }
  catch(error) {
    const errorMessage = error.details
      .map((details) => details.message)
      .join(", ");
    return res.status(400).json({ error: errorMessage });
  }
};

Answered By – Irakli Tchedia

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published