In the new portal project, we implemented a validator to check whether the request data received from the client's request was in an acceptable format to be inserted into our database.
A validator makes CRUD safer and more efficient since it can reject the input data, even before it even hits the controller, and make sure that only the correctly formated data with all necessary information are received.
Also, in this way, your code is cleaner and more modularized, since you put all validation logic in a separate file.
Joi is a very easy-to-use and well-documented library provided by Walmart(!).
npm i joi
Joi is used as a middleware for each route. So, for example, it can go inside middleware
inside routes
const router = require("express").Router()
const controller = require("../controllers/controller")
const validator = require("express-joi-validation").createValidator({
passError: true,
}) //[1]
const validatorFile = require("./middleware/validators/validator") //[2]
router.post(
"/post_something",
...other middlewares,
validator.body(validatorFile.someSchema),
controller.someController
) // [3]
[1] Creates an instance of validator module that can be used to generate middleware.
[2] Import the validator file
[3] Based on what part of request you want to validate, change .body
part to .params
, .query
and etc.
// Example1: Validation for object id
const JoiBase = require("@hapi/joi")
const JoiDate = require("@hapi/joi-date")
const { isValidObjectId } = require("mongoose")
const Joi = JoiBase.extend(JoiDate)
module.exports.objectIdArraySchema = Joi.object({
id: Joi.array()
.items(
Joi.string().custom(
(val, helpers) =>
!isValidObjectId(val)
? helpers.message("Value must be a valid object Id")
: val,
"object Id validation"
)
)
.required(),
})
// Example2: Validation for integer
module.exports.numberSchema = Joi.object({
number: Joi.number().min(0).max(3),
limit: Joi.number()
.default(300)
.min(1)
.max(1000)
.message("Limit must be a value from 1 to 1000."),
offset: Joi.number()
.default(0)
.min(0)
.message("Offset must be a value greater than or equal to zero."),
})
// Example3: Validation for many stuff
const someSchema = {
something: Joi.array()
.items(
Joi.alternatives().try(
Joi.string().alphanum().length(24),
Joi.object({
_id: Joi.string().alphanum().length(24),
name: Joi.string(),
description: Joi.string(),
url: Joi.string(),
})
)
)
.required(),
name: Joi.string().trim().allow(""),
location: Joi.object({
country: Joi.string().trim().allow(""),
continent: Joi.string().trim().allow(""),
}),
updated_at: Joi.date().format("YYYY-MM-DDTHH:mm:ss.SSSZ").raw(),
}
module.exports.someOtherSchema = Joi.object({
type_1: Joi.array().items(Joi.string().trim().required()),
type_2: Joi.array().items(Joi.binary().required()),
Joi.array().items(Joi.boolean().optional()).optional(),
}).nand("type_1", "type_2").options({ stripUnknown: true }
stripUnknown
is used if you want to remove the unknown keys received in request..nand
method if one of fields is requiredalternatives()
: Generates a type that will match one of the provided alternative schemas via the try() method. .custom(method, [description])
: