Przede wszystkim, czym jest validacja i do czego jej potrzebujemy?
Validacją nazywamy szereg czynności polegający na sprawdzeniu zgodności z danymi schematami i wytycznymi, a także naszymi oczekiwaniami i przewidywaniami. Validację stosuje się najczęściej do sprawdzania poprawności danych wejściowych. Moim zdaniem sprawdzanie poprawności otrzymywanych danych jest absolutną koniecznością, jeśli chcemy, by nasza aplikacja działała poprawnie.
Gdzie wykonywać validację?
Czynność tą powinniśmy wykonywać zarówno po stronie klienta, jak i serwera. Podczas wypełniania np. formularza użytkownik powinien otrzymać informację zwrotną, jakie pola uzupełnił błędnie oraz co należy w nich poprawić. Głównym celem jest zwiększenie komfortu użytkownika podczas korzystania z naszej aplikacji. Nie chroni nas to jednak przed umyślnym wprowadzeniem niepoprawnych danych. Mechanizmy znajdujące się po stronie klienta można w banalny sposób obejść. Dlatego też sprawdzanie poprawności danych powinno przede wszystkim odbywać się na serwerze.
Praktyczny przykład validacji na serwerze
Kluczowym zagadnieniem tego artykułu jest omówienie możliwości paczki express-validator
będącej rozszerzeniem frameworka Express.js. Artykuł przygotowano bazując na wersji 5.3.0. W nowszych wersjach biblioteki mogą wystąpić różnice, więc aby być na bieżąco, zachęcam do sprawdzenia odicjalnej dokumentacji. Jak sama nazwa wskazuje, rozszerzenie to umożliwia nam tworzenie validatorów. W tym wpisie będziemy sprawdzać poprawność danych podczas rejestracji nowego użytkownika. Na samym początku musimy zainstalować paczkę express-validator
.
npm install --save express-validator
Po zainstalowaniu pakietu możemy już importować interesujące nas komponenty. Przede wszystkim potrzebujemy skorzystać z funkcji check
. Funkcja check
jako parametr przyjmuje string z nazwą pola, które należy sprawdzić. Potem następuje spora dowolność w validacji. Tutaj możemy korzystać z istniejących w JS funkcji jak chociażby trim
, którą możemy obciąć nadmiar białych znaków. Innym przykładem może być matches
, gdzie możemy skorzystać z wyrażenia regularnego.
Oprócz tego możemy skorzystać z funkcji dostępnych dzięki naszej paczce. Jednymi z najbardziej przydatnych jest isEmpty
, która sprawdza, czy nie otrzymaliśmy pustego ciągu znaków. Możemy też skorzystać z negacji not
, łącząc ją z poprzednią funkcją. Otrzymujemy dzięki temu kombinację sprawdzającą, czy dany ciąg znaków nie jest pusty — niezmiernie przydatne w trakcie validacji. Podobnie do funkcji isEmpty
działają funkcje takie jak: isArray
, isEmail
i isBoolean
. Jeśli chcemy sprawdzać jakąś wartość, ale nie chcemy, aby jej podanie było obowiązkowe, możemy skorzystać z funkcji optional
. Samych funkcji oczywiście jest znacznie więcej. Link do dokumentacji znajdziesz w źródłach na końcu tego wpisu.
Zwieńczeniem validatora jest funkcja withMessage
zwracająca opracowany przez nas komunikat w przypadku, gdy nasze dane nie przejdą validacji. Należy pamiętać, aby komunikat zwrotny jednoznacznie określał, na czym polega błąd, który popełnił użytkownik. Dlatego też w swoich przykładach sprawdzam niektóre pola kilkukrotnie, ponieważ potrzebuję sprawdzić więcej niż jedną rzecz. Każdy przypadek powinien posiadać osobny, komunikat. Zwróć uwagę na pola takie jak password czy email.
import { check } from 'express-validator/check';
exports.validateRegister = [
check('name').trim().not().isEmpty().withMessage('Name is required.'),
check('password').trim().not().isEmpty().withMessage('Password is required.'),
check('password').trim().isLength({ min: 8 }).withMessage('Password must be at least 8 characters long.'),
check('email').trim().not().isEmpty().withMessage('E-mail is required.'),
check('email').trim().isEmail().withMessage('Invalid e-mail address.'),
check('pictures').optional().isArray().withMessage('Invalid data format.')
];
Sanityzacja
Kolejnym krokiem będzie sanityzacja danych. Sama sanityzacja pozwala nam uchronić się np. przed atakami takimi jak na przykład XSS. Zainstalowana przez nas paczka posiada również mechanizmy pozwalające na sanityzację danych i idąc tropem XSS, zamianę znaków specjalnych jak na przykład nawiasy domknięte czy slesze na encje. Do tej czynności posłuży nam funkcja sanitizeBody
, która jako parametr przyjmuje string z nazwą pola. Alternatywnie możemy użyć gwiazdki, jeśli chcemy pracować z każdą przekazaną wartością. Do zamiany na encje posłuży nam funkcja escape
.
import { check } from 'express-validator/check';
import { sanitizeBody } from 'express-validator/filter';
exports.validateRegister = [
check('name').trim().not().isEmpty().withMessage('Name is required.'),
check('password').trim().not().isEmpty().withMessage('Password is required.'),
check('password').trim().isLength({ min: 8 }).withMessage('Password must be at least 8 characters long.'),
check('email').trim().not().isEmpty().withMessage('E-mail is required.'),
check('email').trim().isEmail().withMessage('Invalid e-mail address.'),
check('pictures').optional().isArray().withMessage('Invalid data format.'),
sanitizeBody('*').escape()
];
Customowe funkcje validujące
Omawiana w tym wpisie bibliotek udostępnia również tworzenie niestandardowych filtrów. Służy do tego funkcja custom
. Wewnątrz tej funkcji możemy stworzyć własne funkcje validujące.
W moim projekcie wykorzystałem funkcję custom
do sprawdzania poprawności wpisywanej daty. Niestety sama biblioteka express-validator
nie udostępnia domyślnie takiej możliwości. Do sprawdzenia poprawności daty użyłem biblioteki Moment.js, która jest dedykowana operacjom na dacie i czasie.
import { check } from 'express-validator/check';
import { sanitizeBody } from 'express-validator/filter';
import moment from 'moment';
exports.validateRegister = [
check('name').trim().not().isEmpty().withMessage('Name is required.'),
check('password').trim().not().isEmpty().withMessage('Password is required.'),
check('password').trim().isLength({ min: 8 }).withMessage('Password must be at least 8 characters long.'),
check('email').trim().not().isEmpty().withMessage('E-mail is required.'),
check('email').trim().isEmail().withMessage('Invalid e-mail address.'),
check('birthDate').trim().not().isEmpty().withMessage('Date of birth is required.'),
check('birthDate').trim().custom(value => moment(value, 'YYYY-MM-DD').isValid()).withMessage('Invalid date of birth.'),
check('pictures').optional().isArray().withMessage('Invalid data format.'),
sanitizeBody('*').escape()
];
Implementacja
Mając już stworzone reguły, należy ich użyć. Oprócz samych reguł potrzebujemy jeszcze funkcji, która powiadomi użytkownika, jakie dane wprowadził niepoprawnie i co zależy w nich poprawić. Poniżej znajdziesz gotowe rozwiązanie, które możesz wykorzystać w swoim projekcie.
import {validationResult} from "express-validator/check";
exports.checkValidation = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({
success: false,
data: req.body,
errors: errors.mapped()
});
}
next();
};
Ostatnim krokiem będzie zaimplementowanie stworzonego przez nas kodu. W moim przypadku będzie to router, w którym znajduje się ścieżka do tworzenia nowego konta użytkownika.
import { Router } from "express";
import authController from "../controllers/authController";
import { validateRegister } from "../validators/usersValidator";
import { checkValidation } from "../validators/checkValidation";
const api = Router();
api.post('/register',
validateRegister,
checkValidation,
authController.register
);
module.exports = api;
Więcej przykładów
W tym wpisie nie przedstawiłem wszystkich możliwości express-validatora. Myślę jednak, że zestaw informacji, jaki przedstawiłem, pozwoli na szybkie wsiąknięcie w temat validacji w Express.js.
Przedstawiony w tym wpisie kod jest częścią tworzonego przeze mnie projektu. Jeśli zaciekawił Cię temat validacji, to zapraszam Cię do sprawdzenia mojego repozytorium na GitHub’ie.
Jeśli coś jest niezrozumiałe lub widzisz błąd, to daj mi o tym znać w komentarzu! Zachęcam też do zapoznania się ze źródłami i materiałami dodatkowymi.
Rozumiem, że w obecnej wersji express-validator już nie „sanitizeBody”?
Z tego co widzę, to w najnowszej wersji express-validatora
sanitizeBody
zostało usunięte. W dokumentacji można znaleźć instrukcję migracji do najnowszej wersji. Dzięki za zwrócenie uwagi, dodałem informację z wersją biblioteki, której dotyczy artykuł.a czy mogę użyć check(’text’).replace(’>’, ”), tak żeby mi sprawdził txt czy sa < i pozamieniał je?
Osobiście skorzystałbym raczej z
sanitizeBody('text').escape()
dzięki 🙂