Is using Joi for validation on top of Mongoose good practice?
Asked Answered
F

2

20

I'm developing a RESTful API with Node.js, Mongoose and Koa and I'm a bit stuck on what are the best practices when it comes to schemas and input validation.

Currently I have both a Mongoose and Joi schema for each resource. The Mongoose schema only includes the basic info about the specific resource. Example:

const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    lowercase: true,
  },
  firstName: String,
  lastName: String,
  phone: String,
  city: String,
  state: String,
  country: String,
});

The Joi schema includes details about each property of the object:

{
  email: Joi.string().email().required(),
  firstName: Joi.string().min(2).max(50).required(),
  lastName: Joi.string().min(2).max(50).required(),
  phone: Joi.string().min(2).max(50).required(),
  city: Joi.string().min(2).max(50).required(),
  state: Joi.string().min(2).max(50).required(),
  country: Joi.string().min(2).max(50).required(),
}

The Mongoose schema is used to create new instances of the given resource at endpoint handler level when writing to the database.

router.post('/', validate, routeHandler(async (ctx) => {
  const userObj = new User(ctx.request.body);
  const user = await userObj.save();

  ctx.send(201, {
    success: true,
    user,
  });
}));

The Joi schema is used in validation middleware to validate user input. I have 3 different Joi schemas for each resource, because the allowed input varies depending on the request method (POST, PUT, PATCH).

async function validate(ctx, next) {
  const user = ctx.request.body;
  const { method } = ctx.request;
  const schema = schemas[method];

  const { error } = Joi.validate(user, schema);

  if (error) {
    ctx.send(400, {
      success: false,
      error: 'Bad request',
      message: error.details[0].message,
    });
  } else {
    await next();
  }
}

I am wondering if my current approach of using multiple Joi schemas on top of Mongoose is optimal, considering Mongoose also has built-int validation. If not, what would be some good practices to follow?

Thanks!

Fetich answered 4/4, 2019 at 8:57 Comment(6)
Can you shortly explain why would you use both? After all, Mongoose schemas are very powerful and you can perform complex validations on the input without using joi.Linkwork
I thought about having Joi validation as a middleware at request level, because Mongoose only seems to provide validation at app level when you create/save objects.Fetich
Can you elaborate on the difference between app-level and request-level?Linkwork
By request level I mean when the request is received and before its endpoint logic is executed. That means the request can be terminated immediately if the input doesn't pass validation middleware. By app level I mean at the point of executing endpoint logic. So the request goes through all the middleware and the input is validated when the object is about to be updated in the database.Fetich
@Linkwork both were the same. right?Counterproof
@Counterproof I think that the difference lies in the exact location the validation takes place. If I understand rok's answer correctly, by request level he means the controller should validate the request before it reaches the "heavy" logic code (i.e. the "service") - which he calls the "app level". I tend to agree with this approach. The controller should do a "shallow" validation: check types of fields, maybe match a request against some swagger file, etc. The "service" should take care of more complex validations.Linkwork
C
5

It is a common practice to implement a validation service even if you have mongoose schema. As you stated yourself it will return an validation error before any login is executed on the data. so, it will definitely save some time in that case. Moreover, you get better validation control with joi. But, it highly depends upon your requirement also because it will increase the extra code you have to write which can be avoided without making much difference to the end result.

Cardsharp answered 20/1, 2020 at 4:44 Comment(0)
C
1

IMO, I don't think there's a definite answer to this question. Like what @omer said in the comment section above, Mongoose is powerful enough to stand its own ground.

But if your code's logic/operations after receiving input is pretty heavy and expensive, it won't hurt adding an extra protection in the API layer to prevent your heavy code from running.

Edit: I just found this good answer by a respectable person.

Chema answered 10/2, 2021 at 7:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.