Создание строго типизированных middleware на TypeScript для Express.js

Express.js — популярный веб-фреймворк для Node.js, который позволяет создавать серверные приложения с использованием middleware. Middleware — это функции, обрабатывающие запросы перед их передачей конечным обработчикам. В TypeScript можно строго типизировать middleware, что помогает избежать ошибок и упрощает поддержку кода. В этой статье мы рассмотрим, как создать строго типизированные middleware на TypeScript для Express.js.

Что такое middleware в Express.js?

Middleware — это функции, которые выполняются между получением HTTP-запроса сервером и отправкой ответа клиенту. Эти функции имеют доступ к объектам запроса (req), ответа (res) и следующей функции (next).

Пример базового middleware:

import { Request, Response, NextFunction } from "express";

function exampleMiddleware(req: Request, res: Response, next: NextFunction): void {
    console.log("Middleware выполнен");
    next();
}

Middleware в этом примере просто выводит сообщение в консоль и передаёт управление следующей функции.

Типизация middleware с использованием TypeScript

TypeScript позволяет строго типизировать параметры middleware, чтобы избежать ошибок при обработке данных. Рассмотрим пример:

import { Request, Response, NextFunction } from "express";

// Интерфейс для типизации параметров запроса
interface TypedRequestBody<T> extends Request {
    body: T;
}

// Middleware для проверки данных в теле запроса
function validateBodyMiddleware<T>(schema: (body: T) => boolean) {
    return (req: TypedRequestBody<T>, res: Response, next: NextFunction): void => {
        if (!schema(req.body)) {
            res.status(400).send("Invalid request body");
            return;
        }
        next();
    };
}

// Пример использования:
const validateUserMiddleware = validateBodyMiddleware<{ username: string; password: string }>(
    (body) => !!body.username && !!body.password
);

Объяснение кода:

  • TypedRequestBody<T>: Наследует объект Request и переопределяет тип body, чтобы он соответствовал указанному типу T.
  • validateBodyMiddleware: Универсальная функция, которая принимает схему валидации и возвращает middleware для проверки данных.
  • validateUserMiddleware: Пример использования для проверки данных пользователя.

В больших приложениях может возникнуть необходимость динамического подключения middleware в зависимости от конфигурации или маршрута. Подробнее о типизации динамических модулей можно прочитать в статье Типизация динамических модулей в TypeScript.

Практическое применение строго типизированных middleware

Строгая типизация middleware не только помогает избежать ошибок, но и делает код более понятным и удобным в использовании. Рассмотрим несколько примеров, где middleware строго типизируются с использованием TypeScript, и разберём, как и где эта типизация обеспечивает безопасность кода.

1. Middleware для проверки авторизации

import { Request, Response, NextFunction } from "express";

// Интерфейс для типизации данных пользователя
interface User {
    id: string;
    role: string;
}

// Интерфейс для запроса с данными пользователя
interface AuthenticatedRequest extends Request {
    user?: User;
}

// Строго типизированное middleware для авторизации
function authenticateMiddleware(
    req: AuthenticatedRequest,
    res: Response,
    next: NextFunction
): void {
    const token = req.headers.authorization;

    if (!token) {
        res.status(401).send("Unauthorized");
        return;
    }

    // В реальной системе здесь проводится проверка токена
    req.user = { id: "123", role: "admin" }; // Моделируем добавление пользователя
    next();
}
  • interface AuthenticatedRequest: Расширяет стандартный Request, добавляя строго типизированное поле user, содержащее объект пользователя с идентификатором и ролью.
  • req: AuthenticatedRequest: Типизирует объект запроса, чтобы гарантировать доступ к полю user, добавляемому в middleware.
  • Использование Response и NextFunction: Эти типы, импортируемые из Express, обеспечивают стандартную типизацию остальных параметров.

2. Middleware для добавления уникального идентификатора запроса

import { Request, Response, NextFunction } from "express";
import { v4 as uuidv4 } from "uuid";

// Расширяем Request, добавляя поле requestId
interface RequestWithId extends Request {
    requestId: string;
}

// Middleware для генерации идентификатора запроса
function requestIdMiddleware(
    req: RequestWithId,
    res: Response,
    next: NextFunction
): void {
    req.requestId = uuidv4();
    next();
}

Объяснение типизации:

  • RequestWithId: Расширяет стандартный объект запроса, добавляя строго типизированное поле requestId с типом string.
  • req: RequestWithId: Гарантирует, что после выполнения middleware поле requestId будет доступно в любом обработчике запроса.
  • Middleware выполняет добавление уникального идентификатора к каждому запросу, улучшая трассировку.

Эти примеры демонстрируют, как строгая типизация middleware делает код безопаснее, надёжнее и удобнее для работы в крупных проектах.

Преимущества строгой типизации

Использование TypeScript для типизации middleware даёт следующие преимущества:

  • Сокращение числа ошибок, связанных с неправильными параметрами.
  • Улучшение автодополнения в IDE.
  • Упрощение командной разработки благодаря явному контракту типов.