Использование классов TypeScript и класса-валидатора
Безопасность является серьезной проблемой для любого серверного приложения. И одна из самых важных вещей — не допустить попадания плохих вещей в приложение.
Сегодня мы научимся проверять входящие запросы в экспресс-приложении.
Прежде чем мы начнем…
Я создал профессиональный шаблон с ExpressJS и Typescript. Эта статья является частью этой серии. Все статьи вы найдете ниже.
* Creating a ExpressJS + Typescript Boilerplate * How to setup Linter and Formatter for NodeJS * How to handle multiple environments in NodeJS * Error Handling in NodeJS * Request Validation in NodeJS * Using Docker Professionally with NodeJS * Using Docker for Local Development in NodeJS * Logging in NodeJS * Kubernetes with NodeJS * Security in NodeJS
Давай начнем!
Ручной способ
Есть ручные способы сделать это, проверив каждый параметр body
и убедившись, что они действительны.
Это будет выглядеть примерно следующим образом.
const { body } = req; if (!body.name && body.name.length === 0) { throw new Error(400, "Name is required"); } // do this for every required key
Но мы можем сделать лучше, не так ли? Нам нужен декларативный способ делать вещи, чтобы наша бизнес-логика оставалась чистой.
Но как?
Есть много способов сделать это. Но мы будем использовать класс-валидатор, так как он лучше поддерживает машинопись.
Кроме того, синтаксис баз классов намного проще для понимания.
Давай начнем
Выполните следующую команду, чтобы получить базовое экспресс-приложение с базовой настройкой обработки ошибок.
Если вы хотите узнать больше о том, как мы это сделали, вы можете обратиться здесь
git clone https://github.com/Mohammad-Faisal/nodejs-expressjs-error-handling
Установите зависимости
Давайте сначала установим необходимые зависимости.
yarn add class-validator class-transformer
Затем создайте новый файл с именем CreateUserRequest.ts
и добавьте туда следующий код.
import { IsDefined } from 'class-validator'; export class CreateUserRequest { @IsDefined() userName!: string; }
Вы получите предупреждение о добавлении декораторов. Откройте файл tsconfig.json
и добавьте следующее значение, чтобы исключить это предупреждение.
"compilerOptions": { "experimentalDecorators": true },
Здесь мы использовали @IsDefined()
, декоратор, предоставленный class-validator
, чтобы сообщить, что нам нужно это свойство userName
как обязательную часть объекта запроса.
Преобразовать тело в класс
Мы знаем, что в почтовом запросе запрос. Тело приходит как объект JSON. Мы должны сначала преобразовать это в класс, а затем запустить проверку.
- Преобразуйте тело запроса JSON в класс, используя class-transformer.
- Запустите проверку с помощью class-validator
import { plainToInstance, Expose } from "class-transformer"; import { validate, Matches, IsDefined } from "class-validator"; const converterObject = plainToInstance(req.body); validate(convertedObject).then((errors) => { console.log(errors); });
Давайте добавим этот код в маршрут.
app.post("/create-user", async (req: Request, res: Response, next: NextFunction) => { const convertedObject = plainToInstance(CreateUserRequest, req.body); validate(convertedObject).then((errors) => { if (errors.length > 0) { let rawErrors: string[] = []; for (const errorItem of errors) { rawErrors = rawErrors.concat(...rawErrors, Object.values(errorItem.constraints ?? [])); } const validationErrorText = "Request validation failed!"; next(new BadRequestError(validationErrorText, rawErrors)); } else { res.status(200).send({ message: "Hello World from create user!", }); } }); });
Вы заметите, что мы передаем возможность обработки ошибок промежуточному программному обеспечению обработки ошибок, вызывая функцию next()
. Мы также используем пользовательский объект Error с именем BadRequest
error.
Давайте добавим новый параметр с именем rawErrors в наш класс ApiError
, который является нашим пользовательским классом ошибок, чтобы добавить список ошибок.
export class ApiError extends Error { statusCode: number; rawErrors: string[] = []; constructor(statusCode: number, message: string, rawErrors?: string[]) { super(message); this.statusCode = statusCode; if (rawErrors) this.rawErrors = rawErrors; Error.captureStackTrace(this, this.constructor); } } // new optional class for bad requests export class BadRequestError extends ApiError { constructor(message: string, errors: string[]) { super(StatusCodes.BAD_REQUEST, message, errors); } }
Теперь, если вы запросите URL-адрес http://localhost:3001/create-user
с телом без userName
, вы получите этот ответ.
{ "success": false, "message": "Request validation failed!", "rawErrors": ["userName should not be null or undefined"] }
Замечательно! Теперь мы можем очень легко проверять наши входящие запросы.
Давайте улучшим наш код, создав класс RequestValidator.
import { Request, Response, NextFunction } from 'express'; import { ClassConstructor, plainToInstance } from 'class-transformer'; import { validate } from 'class-validator'; import { BadRequestError } from './ApiError'; export default class RequestValidator { static validate = <T extends object>(classInstance: ClassConstructor<T>) => { return async (req: Request, res: Response, next: NextFunction) => { const convertedObject = plainToInstance(classInstance, req.body); await validate(convertedObject).then((errors) => { if (errors.length > 0) { let rawErrors: string[] = []; for (const errorItem of errors) { rawErrors = rawErrors.concat(...rawErrors, Object.values(errorItem.constraints ?? [])); } const validationErrorText = 'Request validation failed!'; console.log('error found!', rawErrors); next(new BadRequestError(validationErrorText, rawErrors)); } }); next(); }; }; }
Таким образом, мы можем централизовать нашу логику проверки. И давайте использовать его внутри маршрутов.
app.post("/create-user", RequestValidator.validate(CreateUserRequest), async (req: Request, res: Response) => { res.status(200).send({ message: "Hello World from post!", }); });
Итак, теперь мы можем создавать наши объекты проверки и передавать их нашим обработчикам маршрутов для проверки входящих запросов.
Это все на сегодня. Надеюсь, вы сегодня узнали что-то новое!
Репозиторий GitHub:
Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .
Заинтересованы в масштабировании запуска вашего программного обеспечения? Ознакомьтесь с разделом Схема.