Пошук уроків, статей та іншого контенту
Створите власний middleware, підключите його до маршрутів і використаєте залежності через DI.
Middleware — це код, який виконується між отриманням HTTP-запиту та передаванням його контролеру.
Middleware може:
перевіряти або змінювати запит;
додавати дані до об’єкта request;
записувати інформацію про запити в журнал;
завершувати запит одразу;
передавати керування наступному middleware або контролеру через next().
У NestJS middleware може бути:
функцією;
класом, який реалізує інтерфейс NestMiddleware.
Для використання залежностей через DI зручно створювати middleware як клас.
Створимо middleware, який записуватиме в консоль HTTP-метод, URL і час виконання запиту.
import {
Injectable,
NestMiddleware,
} from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {
use(
request: Request,
response: Response,
next: NextFunction,
): void {
const startedAt = Date.now();
response.on('finish', () => {
const duration = Date.now() - startedAt;
console.log(
`${request.method} ${request.originalUrl} — ` +
`${response.statusCode} — ${duration} мс`,
);
});
next();
}
}useМетод use отримує три основні аргументи:
request — об’єкт HTTP-запиту;
response — об’єкт HTTP-відповіді;
next — функція для передавання керування далі.
Виклик next() обов’язковий, якщо middleware не завершує запит самостійно. Без нього запит може залишитися в стані очікування.
Подія finish спрацьовує після того, як відповідь повністю сформована. Завдяки цьому middleware може виміряти тривалість обробки запиту та отримати код відповіді.
Middleware, створене як клас, може отримувати залежності через конструктор.
Створимо окремий сервіс для запису повідомлень:
import { Injectable } from '@nestjs/common';
@Injectable()
export class RequestLoggerService {
log(message: string): void {
console.log(`[HTTP] ${message}`);
}
}Тепер використаємо цей сервіс у middleware:
import {
Injectable,
NestMiddleware,
} from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { RequestLoggerService } from './request-logger.service';
@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {
constructor(
private readonly logger: RequestLoggerService,
) {}
use(
request: Request,
response: Response,
next: NextFunction,
): void {
const startedAt = Date.now();
response.on('finish', () => {
const duration = Date.now() - startedAt;
this.logger.log(
`${request.method} ${request.originalUrl} — ` +
`${response.statusCode} — ${duration} мс`,
);
});
next();
}
}NestJS створить RequestLoggerMiddleware через контейнер залежностей і передасть йому екземпляр RequestLoggerService.
Щоб підключити middleware, модуль має реалізувати інтерфейс NestModule.
Метод configure отримує об’єкт MiddlewareConsumer, за допомогою якого можна вибрати маршрути для middleware.
Створимо контролер із маршрутом GET /users:
import { Controller, Get } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
findAll(): string {
return 'Список користувачів';
}
}Підключимо middleware до всіх маршрутів контролера UsersController:
import {
MiddlewareConsumer,
Module,
NestModule,
} from '@nestjs/common';
import { UsersController } from './users.controller';
import { RequestLoggerMiddleware } from './request-logger.middleware';
import { RequestLoggerService } from './request-logger.service';
@Module({
controllers: [UsersController],
providers: [
RequestLoggerService,
RequestLoggerMiddleware,
],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): void {
consumer
.apply(RequestLoggerMiddleware)
.forRoutes(UsersController);
}
}Тепер middleware виконуватиметься для маршрутів UsersController, зокрема для:
GET /usersПісля запиту в консолі може з’явитися повідомлення:
[HTTP] GET /users — 200 — 3 мсСтруктура файлів:
src/
├── app.module.ts
├── main.ts
├── request-logger.middleware.ts
├── request-logger.service.ts
└── users.controller.tsrequest-logger.service.tsimport { Injectable } from '@nestjs/common';
@Injectable()
export class RequestLoggerService {
log(message: string): void {
console.log(`[HTTP] ${message}`);
}
}request-logger.middleware.tsimport {
Injectable,
NestMiddleware,
} from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { RequestLoggerService } from './request-logger.service';
@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {
constructor(
private readonly logger: RequestLoggerService,
) {}
use(
request: Request,
response: Response,
next: NextFunction,
): void {
const startedAt = Date.now();
response.on('finish', () => {
const duration = Date.now() - startedAt;
this.logger.log(
`${request.method} ${request.originalUrl} — ` +
`${response.statusCode} — ${duration} мс`,
);
});
next();
}
}users.controller.tsimport { Controller, Get } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
findAll(): string {
return 'Список користувачів';
}
}app.module.tsimport {
MiddlewareConsumer,
Module,
NestModule,
} from '@nestjs/common';
import { UsersController } from './users.controller';
import { RequestLoggerMiddleware } from './request-logger.middleware';
import { RequestLoggerService } from './request-logger.service';
@Module({
controllers: [UsersController],
providers: [
RequestLoggerService,
RequestLoggerMiddleware,
],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): void {
consumer
.apply(RequestLoggerMiddleware)
.forRoutes(UsersController);
}
}main.tsimport { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();Після запуску застосунку запит:
GET http://localhost:3000/usersповерне:
Список користувачівА middleware додатково запише інформацію про запит у консоль.
consumer
.apply(RequestLoggerMiddleware)
.forRoutes(UsersController);consumer
.apply(RequestLoggerMiddleware)
.forRoutes('users');Такий запис застосує middleware до маршрутів із шляхом users.
consumer
.apply(RequestLoggerMiddleware)
.forRoutes(UsersController, AdminController);consumer
.apply(RequestLoggerMiddleware)
.forRoutes('*');У такому випадку middleware виконуватиметься для кожного маршруту.
Middleware можна застосувати лише до потрібного HTTP-методу за допомогою RequestMethod:
import {
MiddlewareConsumer,
Module,
NestModule,
RequestMethod,
} from '@nestjs/common';
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): void {
consumer
.apply(RequestLoggerMiddleware)
.forRoutes({
path: 'users',
method: RequestMethod.GET,
});
}
}У цьому прикладі middleware працюватиме лише для:
GET /usersВоно не застосовуватиметься до POST /users.
Іноді middleware потрібно застосувати майже до всіх маршрутів, але виключити окремі шляхи.
consumer
.apply(RequestLoggerMiddleware)
.exclude(
'health',
'metrics',
)
.forRoutes('*');У цьому випадку middleware виконуватиметься для всіх маршрутів, крім:
/health
/metricsMiddleware виконується до методу контролера:
HTTP-запит
↓
Middleware
↓
Контролер
↓
HTTP-відповідьЯкщо підключено кілька middleware, вони виконуються в порядку підключення:
consumer
.apply(
FirstMiddleware,
SecondMiddleware,
)
.forRoutes(UsersController);Порядок буде таким:
FirstMiddleware
↓
SecondMiddleware
↓
UsersControllerКожен middleware має викликати next(), щоб передати керування наступному елементу ланцюжка.
next()Неправильно:
use(
request: Request,
response: Response,
next: NextFunction,
): void {
console.log('Запит отримано');
}У цьому випадку контролер не буде викликаний, а клієнт може не отримати відповідь.
Правильно:
use(
request: Request,
response: Response,
next: NextFunction,
): void {
console.log('Запит отримано');
next();
}Самого класу middleware недостатньо. Його потрібно підключити через MiddlewareConsumer у методі configure.
Якщо middleware використовує сервіс через конструктор, сервіс має бути доступним як provider модуля:
@Module({
providers: [
RequestLoggerService,
RequestLoggerMiddleware,
],
})
export class AppModule {}Якщо контролер має декоратор:
@Controller('users')то маршрут починається з users, а не з повного URL:
.forRoutes('users');Middleware або викликає next(), або самостійно завершує відповідь через response. Не слід одночасно завершувати відповідь і передавати керування далі без потреби.
Middleware обробляє HTTP-запит до або після виконання контролера.
Клас middleware реалізує інтерфейс NestMiddleware.
Логіка middleware розміщується в методі use.
Виклик next() передає керування наступному middleware або контролеру.
Middleware підключається в методі configure через MiddlewareConsumer.
Метод forRoutes визначає маршрути, для яких працюватиме middleware.
Залежності middleware можна отримувати через конструктор і стандартний DI NestJS.