Подробное руководство по пониманию и реализации промежуточного слоя TypeScript в приложениях Express.js. Изучите расширенные шаблоны типов для надежного и удобного в обслуживании кода.
TypeScript Middleware: Осваиваем шаблоны типов промежуточного слоя Express
Express.js, минималистичный и гибкий веб-фреймворк Node.js, позволяет разработчикам создавать надежные и масштабируемые API и веб-приложения. TypeScript улучшает Express, добавляя статическую типизацию, улучшая поддерживаемость кода и выявляя ошибки на ранней стадии. Функции промежуточного слоя являются краеугольным камнем Express, позволяя вам перехватывать и обрабатывать запросы до того, как они достигнут обработчиков маршрутов. В этой статье рассматриваются расширенные шаблоны типов TypeScript для определения и использования промежуточного слоя Express, повышения безопасности типов и ясности кода.
Понимание промежуточного слоя Express
Функции промежуточного слоя — это функции, которые имеют доступ к объекту запроса (req), объекту ответа (res) и следующей функции промежуточного слоя в цикле «запрос-ответ» приложения. Функции промежуточного слоя могут выполнять следующие задачи:
- Выполнять любой код.
- Вносить изменения в объекты запроса и ответа.
- Завершать цикл «запрос-ответ».
- Вызывать следующую функцию промежуточного слоя в стеке.
Функции промежуточного слоя выполняются последовательно по мере их добавления в приложение Express. Общие варианты использования промежуточного слоя включают:
- Регистрацию запросов.
- Аутентификацию пользователей.
- Авторизацию доступа к ресурсам.
- Проверку данных запроса.
- Обработку ошибок.
Базовый промежуточный слой TypeScript
В базовом приложении TypeScript Express функция промежуточного слоя может выглядеть так:
import { Request, Response, NextFunction } from 'express';
function loggerMiddleware(req: Request, res: Response, next: NextFunction) {
console.log(`Request: ${req.method} ${req.url}`);
next();
}
export default loggerMiddleware;
Этот простой промежуточный слой регистрирует метод запроса и URL-адрес в консоли. Давайте разберем аннотации типов:
Request: представляет объект запроса Express.Response: представляет объект ответа Express.NextFunction: функция, которая при вызове выполняет следующий промежуточный слой в стеке.
Вы можете использовать этот промежуточный слой в своем приложении Express следующим образом:
import express from 'express';
import loggerMiddleware from './middleware/loggerMiddleware';
const app = express();
const port = 3000;
app.use(loggerMiddleware);
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
Расширенные шаблоны типов для промежуточного слоя
Хотя базовый пример промежуточного слоя является функциональным, ему не хватает гибкости и безопасности типов для более сложных сценариев. Давайте рассмотрим расширенные шаблоны типов, которые улучшают разработку промежуточного слоя с помощью TypeScript.
1. Пользовательские типы запросов/ответов
Часто вам потребуется расширить объекты Request или Response пользовательскими свойствами. Например, после аутентификации вы можете захотеть добавить свойство user в объект Request. TypeScript позволяет расширять существующие типы с помощью слияния объявлений.
// src/types/express/index.d.ts
import { Request as ExpressRequest } from 'express';
declare global {
namespace Express {
interface Request {
user?: {
id: string;
email: string;
// ... other user properties
};
}
}
}
export {}; // This is needed to make the file a module
В этом примере мы расширяем интерфейс Express.Request, чтобы включить необязательное свойство user. Теперь, в вашем промежуточном слое аутентификации, вы можете заполнить это свойство:
import { Request, Response, NextFunction } from 'express';
function authenticationMiddleware(req: Request, res: Response, next: NextFunction) {
// Simulate authentication logic
const userId = req.headers['x-user-id'] as string; // Or fetch from a token, etc.
if (userId) {
// In a real application, you would fetch the user from a database
req.user = {
id: userId,
email: `user${userId}@example.com`
};
next();
} else {
res.status(401).send('Unauthorized');
}
}
export default authenticationMiddleware;
А в обработчиках маршрутов вы можете безопасно получить доступ к свойству req.user:
import express from 'express';
import authenticationMiddleware from './middleware/authenticationMiddleware';
const app = express();
const port = 3000;
app.use(authenticationMiddleware);
app.get('/profile', (req: Request, res: Response) => {
if (req.user) {
res.send(`Hello, ${req.user.email}! Your user ID is ${req.user.id}`);
} else {
// This should never happen if the middleware is working correctly
res.status(500).send('Internal Server Error');
}
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
2. Фабрики промежуточного слоя
Фабрики промежуточного слоя — это функции, которые возвращают функции промежуточного слоя. Этот шаблон полезен, когда вам нужно настроить промежуточный слой с определенными параметрами или зависимостями. Например, рассмотрим промежуточный слой ведения журнала, который регистрирует сообщения в определенном файле:
import { Request, Response, NextFunction } from 'express';
import fs from 'fs';
import path from 'path';
function createLoggingMiddleware(logFilePath: string) {
return (req: Request, res: Response, next: NextFunction) => {
const logMessage = `[${new Date().toISOString()}] Request: ${req.method} ${req.url}\n`;
fs.appendFile(logFilePath, logMessage, (err) => {
if (err) {
console.error('Error writing to log file:', err);
}
next();
});
};
}
export default createLoggingMiddleware;
Вы можете использовать эту фабрику промежуточного слоя следующим образом:
import express from 'express';
import createLoggingMiddleware from './middleware/loggingMiddleware';
const app = express();
const port = 3000;
const logFilePath = path.join(__dirname, 'logs', 'requests.log');
app.use(createLoggingMiddleware(logFilePath));
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
3. Асинхронный промежуточный слой
Функциям промежуточного слоя часто необходимо выполнять асинхронные операции, такие как запросы к базе данных или вызовы API. Чтобы правильно обрабатывать асинхронные операции, необходимо убедиться, что функция next вызывается после завершения асинхронной операции. Это можно сделать с помощью async/await или Promises.
import { Request, Response, NextFunction } from 'express';
async function asyncMiddleware(req: Request, res: Response, next: NextFunction) {
try {
// Simulate an asynchronous operation
await new Promise(resolve => setTimeout(resolve, 100));
console.log('Asynchronous operation completed');
next();
} catch (error) {
next(error); // Pass the error to the error handling middleware
}
}
export default asyncMiddleware;
Важно: не забудьте обработать ошибки в своем асинхронном промежуточном слое и передать их в промежуточный слой обработки ошибок с помощью next(error). Это гарантирует правильную обработку и регистрацию ошибок.
4. Промежуточный слой обработки ошибок
Промежуточный слой обработки ошибок — это особый тип промежуточного слоя, который обрабатывает ошибки, возникающие во время цикла «запрос-ответ». Функции промежуточного слоя обработки ошибок имеют четыре аргумента: err, req, res и next.
import { Request, Response, NextFunction } from 'express';
function errorHandler(err: any, req: Request, res: Response, next: NextFunction) {
console.error(err.stack);
res.status(500).send('Something went wrong!');
}
export default errorHandler;
Вы должны зарегистрировать промежуточный слой обработки ошибок после всех других промежуточных слоев и обработчиков маршрутов. Express идентифицирует промежуточный слой обработки ошибок по наличию четырех аргументов.
import express from 'express';
import asyncMiddleware from './middleware/asyncMiddleware';
import errorHandler from './middleware/errorHandler';
const app = express();
const port = 3000;
app.use(asyncMiddleware);
app.get('/', (req, res) => {
throw new Error('Simulated error!'); // Simulate an error
});
app.use(errorHandler); // Error handling middleware MUST be registered last
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
5. Промежуточный слой проверки запросов
Проверка запросов является важным аспектом создания безопасных и надежных API. Промежуточный слой можно использовать для проверки входящих данных запроса и обеспечения соответствия определенным критериям, прежде чем они достигнут обработчиков маршрутов. Для проверки запросов можно использовать такие библиотеки, как joi или express-validator.
Вот пример с использованием express-validator:
import { Request, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator';
const validateCreateUserRequest = [
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
}
];
export default validateCreateUserRequest;
Этот промежуточный слой проверяет поля email и password в теле запроса. Если проверка не удалась, возвращается ответ 400 Bad Request с массивом сообщений об ошибках. Вы можете использовать этот промежуточный слой в обработчиках маршрутов следующим образом:
import express from 'express';
import validateCreateUserRequest from './middleware/validateCreateUserRequest';
const app = express();
const port = 3000;
app.post('/users', validateCreateUserRequest, (req, res) => {
// If validation passes, create the user
res.send('User created successfully!');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
6. Внедрение зависимостей для промежуточного слоя
Когда ваши функции промежуточного слоя зависят от внешних служб или конфигураций, внедрение зависимостей может помочь улучшить тестируемость и поддерживаемость. Вы можете использовать контейнер внедрения зависимостей, такой как tsyringe, или просто передавать зависимости в качестве аргументов в свои фабрики промежуточного слоя.
Вот пример с использованием фабрики промежуточного слоя с внедрением зависимостей:
// src/services/UserService.ts
export class UserService {
async createUser(email: string, password: string): Promise {
// In a real application, you would save the user to a database
console.log(`Creating user with email: ${email} and password: ${password}`);
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate a database operation
}
}
// src/middleware/createUserMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import { UserService } from '../services/UserService';
function createCreateUserMiddleware(userService: UserService) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password } = req.body;
await userService.createUser(email, password);
res.status(201).send('User created successfully!');
} catch (error) {
next(error);
}
};
}
export default createCreateUserMiddleware;
// src/app.ts
import express from 'express';
import createCreateUserMiddleware from './middleware/createUserMiddleware';
import { UserService } from './services/UserService';
import errorHandler from './middleware/errorHandler';
const app = express();
const port = 3000;
app.use(express.json()); // Parse JSON request bodies
const userService = new UserService();
const createUserMiddleware = createCreateUserMiddleware(userService);
app.post('/users', createUserMiddleware);
app.use(errorHandler);
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
Рекомендации по промежуточному слою TypeScript
- Держите функции промежуточного слоя небольшими и сфокусированными. Каждая функция промежуточного слоя должна иметь одну задачу.
- Используйте описательные имена для своих функций промежуточного слоя. Имя должно четко указывать, что делает промежуточный слой.
- Правильно обрабатывайте ошибки. Всегда перехватывайте ошибки и передавайте их в промежуточный слой обработки ошибок с помощью
next(error). - Используйте пользовательские типы запросов/ответов для повышения безопасности типов. Расширяйте интерфейсы
RequestиResponseпользовательскими свойствами по мере необходимости. - Используйте фабрики промежуточного слоя для настройки промежуточного слоя с определенными параметрами.
- Документируйте свои функции промежуточного слоя. Объясните, что делает промежуточный слой и как его следует использовать.
- Тщательно протестируйте свои функции промежуточного слоя. Напишите модульные тесты, чтобы убедиться, что ваши функции промежуточного слоя работают правильно.
Заключение
TypeScript значительно улучшает разработку промежуточного слоя Express, добавляя статическую типизацию, улучшая поддерживаемость кода и выявляя ошибки на ранней стадии. Освоив расширенные шаблоны типов, такие как пользовательские типы запросов/ответов, фабрики промежуточного слоя, асинхронный промежуточный слой, промежуточный слой обработки ошибок и промежуточный слой проверки запросов, вы можете создавать надежные, масштабируемые и типобезопасные приложения Express. Не забывайте следовать передовым практикам, чтобы ваши функции промежуточного слоя оставались небольшими, сфокусированными и хорошо документированными.