Использование классов 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. Мы должны сначала преобразовать это в класс, а затем запустить проверку.

  1. Преобразуйте тело запроса JSON в класс, используя class-transformer.
  2. Запустите проверку с помощью 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 .

Заинтересованы в масштабировании запуска вашего программного обеспечения? Ознакомьтесь с разделом Схема.