Middleware

~15 daq · NestJS · Boshlang'ich daraja

Middleware nima?

Tasavvur qiling: siz katta bir ofisga kiryapsiz. Kirish eshigida xavfsizlik xodimi turibdi. U har bir kelgan odamni tekshiradi — hujjatni ko'radi, ro'yxatga yozadi, keyin ichkariga o'tkazadi. Ba'zilarni o'tkazmaydi. Bu xavfsizlik xodimi — Middleware.

NestJS da Middleware — so'rov Controller ga yetib borishidan oldin ishlaydigan funksiya. U so'rovni ko'radi, xohlasa o'zgartiradi, keyin keyingi bosqichga o'tkazadi yoki to'xtatadi.

Foydalanuvchi so'rovi
        ↓
[ Middleware ] ← tekshirish, loglash, o'zgartirish
        ↓
[ Controller ]
        ↓
[ Service ]
        ↓
      Javob

Middleware va boshqa tushunchalar farqi

NestJS da bir nechta o'xshash tushunchalar bor — Middleware, Guard, Interceptor. Bularni aralashtirmaslik kerak:

Middleware — so'rov kelganida eng birinchi ishlaydi. Request va Response ga to'liq kirish imkoni bor. Express Middleware bilan bir xil.

Guard — foydalanuvchining ruxsati bormi yo'qmi — tekshiradi. Keyingi darslarda o'rganamiz.

Interceptor — so'rov ham, javob ham kelganda ishlaydi. Ikkala tomonda ham o'zgartirish qilishi mumkin.

Middleware eng sodda va eng erta ishlaydi. Shuning uchun loglash, so'rovni tekshirish, header qo'shish kabi ishlar uchun ishlatiladi.


Birinchi Middleware

CLI bilan yaratish:

nest g middleware common/middleware/logger --no-spec
// common/middleware/logger.middleware.ts
import { Injectable, NestMiddleware, Logger } from "@nestjs/common";
import { Request, Response, NextFunction } from "express";

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  private readonly logger = new Logger("HTTP");

  use(req: Request, res: Response, next: NextFunction) {
    // So'rov keldi — loglaymiz
    const { method, originalUrl } = req;
    const start = Date.now();

    this.logger.log(`→ ${method} ${originalUrl}`);

    // So'rov tugaganda javobni loglaymiz
    res.on("finish", () => {
      const duration = Date.now() - start;
      const { statusCode } = res;

      this.logger.log(
        `← ${method} ${originalUrl} ${statusCode} — ${duration}ms`,
      );
    });

    // Keyingi bosqichga o'tkazamiz
    next();
  }
}

Bu yerda uchta parametr bor — ularni to'liq tushunish muhim:

req — Request ob'ekti. Foydalanuvchidan kelgan hamma narsa shu yerda: URL, method, header lar, body, cookie lar. Siz buni o'qiy olasiz, o'zgartira olasiz.

res — Response ob'ekti. Foydalanuvchiga qaytariladigan javob. Siz uni to'xtatib, o'zingiz javob qaytarishingiz mumkin.

next — funksiya. Uni chaqirsangiz — so'rov keyingi bosqichga o'tadi. Chaqirmasangiz — so'rov bu yerda qolib ketadi, foydalanuvchi javob olmaydi. Bu juda muhim — next() ni unutmang.


Middleware ni ulash

Middleware ni Module da ulanadi. Lekin boshqa narsalardan farqli — @Module decorator ichida emas, balki configure metodi orqali:

// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from "@nestjs/common";
import { LoggerMiddleware } from "./common/middleware/logger.middleware";
import { UsersModule } from "./users/users.module";

@Module({
  imports: [UsersModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware) // qaysi Middleware
      .forRoutes("*"); // qaysi route larga
  }
}

NestModule — bu interfeys. Uni implement qilganda configure metodini yozish shart. NestJS bu metodga MiddlewareConsumer ob'ektini beradi — u orqali Middleware ni qayerga ulashni belgilaysiz.

forRoutes('*') — barcha route larga qo'llash. * — wildcard, ya'ni hamma narsa.

Terminalda endi har bir so'rov loglanadi:

[HTTP] → GET /users
[HTTP] ← GET /users 200 — 12ms
[HTTP] → POST /users
[HTTP] ← POST /users 201 — 45ms

forRoutes — qaysi route larga qo'llash

Middleware ni barcha route larga emas, faqat ma'lum route larga qo'llash mumkin. Bir nechta usul bor:

String bilan:

consumer.apply(LoggerMiddleware).forRoutes("users"); // faqat /users va uning ostidagi route lar

Metod bilan birga:

import { RequestMethod } from "@nestjs/common";

consumer.apply(LoggerMiddleware).forRoutes({
  path: "users",
  method: RequestMethod.GET, // faqat GET so'rovlari
});

Controller bilan:

import { UsersController } from "./users/users.controller";

consumer.apply(LoggerMiddleware).forRoutes(UsersController); // faqat UsersController dagi route lar

Controller bilan ishlatish eng qulay — route larni qo'lda yozishingiz shart emas. Controller o'zgarganda Middleware avtomatik yangi route larni ham qamrab oladi.


exclude — ba'zi route larni o'tkazib yuborish

Ba'zan Middleware ni deyarli barcha route larga qo'llashingiz kerak, lekin bir-ikkitasini istisno qilishingiz kerak. Masalan, health check endpoint i uchun loglash shart emas:

consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: "health", method: RequestMethod.GET },
    { path: "metrics", method: RequestMethod.GET },
  )
  .forRoutes("*");

exclude — bu route larga Middleware qo'llanmaydi. Qolgan barcha route larga qo'llanadi.


Bir nechta Middleware

apply ga bir nechta Middleware berilishi mumkin. Ular chapdan o'ngga ketma-ket ishlaydi:

consumer.apply(LoggerMiddleware, AuthMiddleware, CorsMiddleware).forRoutes("*");

Avval LoggerMiddleware ishlaydi, keyin AuthMiddleware, keyin CorsMiddleware. Har biri next() chaqirsa — keyingisi boshlanadi. Birontasi next() chaqirmasa — keyingisi hech qachon ishlamaydi.


Funksional Middleware

Har doim sinf yozish shart emas. Agar Middleware oddiy va dependency injection kerak bo'lmasa — oddiy funksiya yozish mumkin:

// common/middleware/cors.middleware.ts
import { Request, Response, NextFunction } from "express";

export function corsMiddleware(
  req: Request,
  res: Response,
  next: NextFunction,
) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,PATCH");
  res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
  next();
}

Ulash xuddi sinf Middleware singari:

consumer.apply(corsMiddleware).forRoutes("*");

Sinf va funksiya Middleware ning farqi bitta — sinf @Injectable() bilan belgilanadi va constructor da dependency injection ishlatishi mumkin. Funksiya — yo'q.


Amaliy misol — so'rovni boyitish

Middleware faqat loglash uchun emas. Ba'zan so'rovga qo'shimcha ma'lumot qo'shiladi. Masalan, so'rov kelgan vaqtni qo'shish:

// common/middleware/request-time.middleware.ts
import { Injectable, NestMiddleware } from "@nestjs/common";
import { Request, Response, NextFunction } from "express";

@Injectable()
export class RequestTimeMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // So'rov ob'ektiga yangi maydon qo'shamiz
    (req as any).requestTime = new Date().toISOString();

    next();
  }
}

Controller da bu ma'lumotni olish:

@Get()
findAll(@Req() req: Request) {
  return {
    data: [],
    requestTime: (req as any).requestTime,
  };
}

Javob:

{
  "data": [],
  "requestTime": "2024-01-15T10:30:00.000Z"
}

Xulosa

Middleware — so'rov va Controller orasidagi "darvoza qo'riqchisi". U so'rovni ko'radi, tekshiradi, o'zgartiradi yoki to'xtatadi. Eng muhimi — next() chaqirilmasa so'rov keyinga o'tmaydi.

Eslab qoling:

  • use(req, res, next) — uchala parametr har doim bo'ladi
  • next() — chaqirilmasa so'rov qolib ketadi
  • configure metodi — Middleware ni Module da ulash joyi
  • forRoutes — qaysi route larga qo'llash
  • exclude — qaysi route larni o'tkazib yuborish
  • Bir nechta Middleware — chapdan o'ngga ketma-ket ishlaydi
  • Sinf Middleware — DI ishlatishi mumkin
  • Funksional Middleware — DI shart bo'lmagan oddiy holatlar uchun

Keyingi dars

9-dars: Guards — Authorization, Role-based