Express-validator in Separate File May 13th 2020 Words: 562

Background

The official document of express-validator gives an example usage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ...rest of the initial code omitted for simplicity.
const { check, validationResult } = require('express-validator');

app.post('/user', [
// username must be an email
check('username').isEmail(),
// password must be at least 5 chars long
check('password').isLength({ min: 5 })
], (req, res) => {
// Finds the validation errors in this request and wraps them in an object with handy functions
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}

User.create({
username: req.body.username,
password: req.body.password
}).then(user => res.json(user));
});

All the rules of a path are written as middleware, which coupled tightly with the router. This pattern may be fine for simple use case, but will make the maintenance difficult for a complex project.

In this article, I am going to use one module validation.js file to hold validation rules etc. and present the function as a middleware.

All in validation.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import expressValidator from "express-validator";
import pathToRegexp from "path-to-regexp";

const validateBody = expressValidator.body;
const validateParam = expressValidator.param;
const validateQuery = expressValidator.query;
const validationResult = expressValidator.validationResult;

const validateRules = [
{
path: "/oauth/token",
method: "POST",
mw: [
validateBody("grant_type").equals("password").withMessage("must be 'password'"),
validateBody("username").isAlphanumeric().isLength({max: 320}).withMessage("must be alphanumeric string length < 320"),
validateBody("password").isMD5().withMessage("must be MD5"),
validateBody("scope").isIn(["admin", "user"]).withMessage("must be one of 'admin', 'user'")
]
},
{
path: "/oauth/refresh",
method: "POST",
mw: [
validateBody("grant_type").equals("refresh_token").withMessage("must be 'refresh_token'"),
validateBody("refresh_token").isJWT().withMessage("must be JWT")
]
},
{
path: "/user/:id",
method: "POST",
mw: [
validateBody("content").isJSON().withMessage("must be object")
]
}
];

const validate = async (req, res, next) => {
const path = req.path,
method = req.method;
const validateRule = validateRules.find(x => {
if (x.method !== method) return false;
const regexp = pathToRegexp.pathToRegexp(x.path);
return regexp.test(path);
});
if (!validateRule) return next();
try {
for (const func of validateRule.mw) {
await func(req, res, () => void 0);
}
} catch (e) {
return next(e);
}
const formatter = ({location, msg, param, value, nestedErrors}) => {
return `[${location}]${param}: ${msg}`;
};
const errors = validationResult(req).formatWith(formatter);
if (!errors.isEmpty()) {
res.status(400).json({
status: 400,
msg: errors.array({onlyFirstError: true})[0]
});
} else {
return next();
}
};

export default validate;

Use validator as middleware in router.js

Use for individual router:

1
2
3
4
5
6
7
8
9
10
import validate from "./validation.js";

// ... rest of the code is omitted for simplicity
router.post("/oauth/token", [validate], async (req, res, next) => {
try {
await core.signJwt(req, res);
} catch (e) {
return next(e);
}
});

Or use for all path:

1
2
3
4
import validate from "./validation.js";

// ... rest of the code is omitted for simplicity
app.use(validate);

EOF