Walidacja requestów w ExpressJS
W tym wpisie chciałbym przybliżyć Ci niezwykle wygodną bibliotekę walidacji requestów do API napisanych w frameworku Express.js.
JOI, bo tak nazywa się wyżej wspomniana biblioteka, została stworzona przez ekipę odpowiadającą za framework HapiJS, ale można jej też użyć w Express.js, dzięki bibliotece express-validation.
JOI pozwala walidować dane przychodzące w parametrach, nagłówku, body, a nawet plikach cookies. Posiada kilkadziesiąt wbudowanych walidatorów i z każdą wersją ich ilość wzrasta, a jeśli to nam nie wystarcza, możemy też w bardzo prosty sposób dodać własne reguły walidacji.
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
const Joi = require('joi');
const customJoi = Joi.extend((joi) => ({
base: joi.number(),
name: 'number',
language: {
round: 'needs to be a nip number',
},
rules: [
{
name: 'nip',
validate(params, value, state, options) {
const reg = /^[0-9]{10}$/;
if(reg.test(value) == false) {
return this.createError('number.nip', { v: value }, state, options);
} else {
const digits = (""+value).split("");
var checksum = (6*parseInt(digits[0]) + 5*parseInt(digits[1]) + 7*parseInt(digits[2]) + 2*parseInt(digits[3]) + 3*parseInt(digits[4]) + 4*parseInt(digits[5]) + 5*parseInt(digits[6]) + 6*parseInt(digits[7]) + 7*parseInt(digits[8]))%11;
if (parseInt(digits[9]) !== checksum) {
return this.createError('number.nip', { v: value }, state, options);
}
}
return value;
}
}
]
}));
const schema = customJoi.number().nip();
Poza tym, z pomocą JOI możesz także dokonywać operacji konwersji na danych przychodzących, np. usunąć nadmierne białe znaki:
1
const schema = Joi.string().valid('a').trim();
Bardzo przydatną opcją jest też możliwość definiowania walidacji jednego parametru, na podstawie wartości innego parametru:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const schema = Joi.object().keys({
country: Joi.bool().required(),
allCreditCards: Joi.array().when('hasCreditCards', {
is: true,
then: Joi.array().items({
type: Joi
.string()
.valid(['Visa', 'Mastercard'])
.invalid('Discover')
.required(),
balance: Joi.number().required(),
payment: Joi.number().required()
})
})
});
Dzięki rozszerzeniu express-validation, walidację możemy podpinać nie tylko pod treść requestu (body), ale też pod parametry, query, nagłówki, a nawet pod pliki cookies.
1
2
3
4
5
6
7
8
9
10
11
const user = {
query: {
id: Joi.string().regex(/^[0-9a-fA-F]{24}$/).required(),
},
body: {
email: Joi.string().email({ minDomainAtoms: 2 }).required(),
firstName: Joi.string().required(),
lastName: Joi.string().required(),
password: Joi.string().required(),
}
};
W poniższych przykładach podziałamy na moim repozytorium REST API dla aplikacji budżetu domowego.
W pierwszej kolejności musimy zainstalować potrzebne rozszerzenia:
1
2
npm i --save express-validation
npm i --save hapijs/joi
Następnie tworzymy plik definiujący reguły walidacji dla routingu w Express.js.
Ja stworzyłem katalog src/config/validator
i umieściłem w nim pliki z nazwami kontrolerów, które zamierzam walidować.
Oto jeden z plików konfiguracji schematu walidowania. src/config/validator/transaction.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
const Joi = require('joi');
const transaction = {
body: {
category: Joi.string().regex(/^[0-9a-fA-F]{24}$/).required(),
account: Joi.string().regex(/^[0-9a-fA-F]{24}$/).required(),
contractor: Joi.string().regex(/^[0-9a-fA-F]{24}$/).required(),
income: Joi.number().allow(null),
expense: Joi.number().allow(null),
description: Joi.string().allow(null)
}
};
const create = { ...transaction };
const update = {
params: {
id: Joi.string().regex(/^[0-9a-fA-F]{24}$/).required(),
},
...transaction
};
const get = {
params: {
id: Joi.string().regex(/^[0-9a-fA-F]{24}$/).required()
}
};
const remove = {
params: {
id: Joi.string().regex(/^[0-9a-fA-F]{24}$/).required()
}
};
const list = {
params: {
category: Joi.string().regex(/^[0-9a-fA-F]{24}$/).optional().allow(null),
contractor: Joi.string().regex(/^[0-9a-fA-F]{24}$/).optional().allow(null),
dateFrom: Joi.date().optional(),
dateTo: Joi.date().optional(),
}
};
const summary = {
params: {
category: Joi.string().regex(/^[0-9a-fA-F]{24}$/).optional().allow(null),
contractor: Joi.string().regex(/^[0-9a-fA-F]{24}$/).optional().allow(null),
dateFrom: Joi.date().optional(),
dateTo: Joi.date().optional(),
}
};
module.exports = {
create,
update,
get,
remove,
list,
summary
};
W tym konkretnym przypadku jako definicję identyfikatorów, wykorzystałem regex do określenia id z bazy MongoDB.
Metoda optional
oznacza, że parametr nie musi zostać podany. Metoda allow(null)
zezwala na podanie null
, np. gdy kontrahent transakcji nie został wybrany.
Teraz wystarczy wykorzystać tak przygotowany plik walidacji w deklaracji wywołań HTTP zadeklarowanych w kontrolerach aplikacji.
src/config/router/transaction.js
1
2
3
4
5
6
7
8
9
10
11
12
13
const { Router } = require('express');
const TransactionController = require('../../controllers/transaction');
const { authJwt } = require('../../services/auth');
const validator = require('express-validation');
const validatorSchema = require('../validator/transaction');
const router = new Router();
// crud operations
router.post("/transaction", authJwt, validator(validatorSchema.create), TransactionController.create);
router.get("/transaction/:id", authJwt, validator(validatorSchema.get), TransactionController.get);
router.put('/transaction/:id', authJwt, validator(validatorSchema.update), TransactionController.update);
router.delete('/transaction/:id', authJwt, validator(validatorSchema.remove), TransactionController.remove);
I to by było na tyle.
W przypadku niespełnienia reguł walidacji, JOI zwróci status 400
odpowiedzi HTTP oraz listę błędów w postaci JSON`a z tablicą błędów, a więc dokładnie to, czego od tego typu narzędzia oczekujemy.
Podsumowanie
Co tu dużo pisać, JOI to na prawdę dobre, sprawnie rozwijane narzędzie do walidacji, którym możesz zweryfikować poprawność requestów lub w bardziej zaawansowanych zastosowaniach - dane wędrujące pomiędzy warstwami aplikacji.
Link do JOI
Link do express-validation
Link do repozytorium, w którym namiętnie wykorzystuję bibliotekę JOI.