15
votes

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!

2
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.omer
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.rok
Can you elaborate on the difference between app-level and request-level?omer
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.rok
@omer both were the same. right?the_haystacker

2 Answers

3
votes

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.

0
votes

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.