Interceptors — Response Mapping, Logging
Interceptor nima?
Hozirgacha o'rgangan narsalarimizni eslaylik:
- Middleware — so'rov kelganida ishga tushadi, faqat so'rovni ko'radi
- Guard — foydalanuvchining ruxsatini tekshiradi
- Pipe — kelgan ma'lumotni tekshiradi va o'zgartiradi
Interceptor esa bulardan farqli — u ikki tomonda ham ishlaydi. Ham so'rov kelayotganda, ham javob ketayotganda. Bu unga boshqalarga qilib bo'lmaydigan narsalarni qilish imkonini beradi.
Hayotiy misol: siz telefon qo'ng'iroq qilyapsiz. O'rtada tarjimon o'tiribdi. U sizning gapingizni eshitadi, tarjima qiladi, yuboradi. Javob kelganda ham xuddi shunday — tarjima qilib sizga yetkazadi. Interceptor — shu tarjimon.
So'rov keldi
↓
[ Interceptor ] ← so'rovni ko'radi, o'zgartirishi mumkin
↓
[ Controller ]
↓
[ Service ]
↓
[ Interceptor ] ← javobni ko'radi, o'zgartirishi mumkin
↓
Javob ketdi
Interceptor ning tuzilishi
Interceptor NestInterceptor interfeysini implement qiladi. Bu interfeys bitta metod talab qiladi — intercept:
// common/interceptors/logging.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger("Interceptor");
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { method, url } = request;
const start = Date.now();
this.logger.log(`→ ${method} ${url} — so'rov keldi`);
// next.handle() — keyingi bosqichga o'tkazadi (Controller ga)
// pipe() — javob qaytganda nima qilishni belgilaydi
return next.handle().pipe(
tap(() => {
const duration = Date.now() - start;
this.logger.log(`← ${method} ${url} — ${duration}ms da tugadi`);
}),
);
}
}
Bu yerda ikki yangi tushuncha bor — CallHandler va Observable. Ularni tushunish muhim.
CallHandler — bu "keyingi bosqich" ni boshqaruvchi ob'ekt. next.handle() chaqirilsa — so'rov Controller ga o'tadi va u yerdan javob qaytadi. Chaqirilmasa — Controller hech qachon ishlamaydi.
Observable — bu RxJS kutubxonasidan. NestJS da javob Observable sifatida qaytadi. Uni pipe() va turli operatorlar bilan o'zgartirish mumkin. Hozircha shuni biling: next.handle() — Controller ning javobi, .pipe() — o'sha javob bilan nima qilish.
tap() — javobni o'zgartirmaydi, faqat "ko'radi". Loglash, statistika yig'ish uchun ishlatiladi.
Response Mapping — javobni o'zgartirish
Haqiqiy loyihalarda barcha API javoblari bir xil formatda bo'lishi kerak. Masalan:
{
"success": true,
"data": { ... },
"timestamp": "2024-01-15T10:30:00.000Z"
}
Lekin Controller shunchaki ma'lumotni qaytaradi:
@Get()
findAll() {
return [{ id: 1, name: 'Jasur' }]; // faqat ma'lumot
}
Har bir Controller da bu formatni qo'lda yozish — juda ko'p takrorlanish. Interceptor buni bir joyda hal qiladi:
// common/interceptors/transform.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
// map() — javobni o'zgartiradi
map((data) => ({
success: true,
data,
timestamp: new Date().toISOString(),
})),
);
}
}
map() — tap() dan farqli. U javobni o'zgartiradi. Controller [{ id: 1, name: 'Jasur' }] qaytargan bo'lsa — map() uni yangi formatga o'raydi.
Endi foydalanuvchi quyidagini ko'radi:
{
"success": true,
"data": [{ "id": 1, "name": "Jasur" }],
"timestamp": "2024-01-15T10:30:00.000Z"
}
Controller da hech narsa o'zgarmadi. Interceptor hamma ishni qildi.
Interceptor ni ulash
Xuddi Guard kabi — uch joyda ulanishi mumkin.
Global darajasida — butun ilova uchun, main.ts da:
// main.ts
import { TransformInterceptor } from "./common/interceptors/transform.interceptor";
import { LoggingInterceptor } from "./common/interceptors/logging.interceptor";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(
new LoggingInterceptor(),
new TransformInterceptor(),
);
await app.listen(3000);
}
Controller darajasida:
import { Controller, UseInterceptors } from "@nestjs/common";
import { TransformInterceptor } from "../common/interceptors/transform.interceptor";
@Controller("users")
@UseInterceptors(TransformInterceptor)
export class UsersController {}
Metod darajasida:
import { Get, UseInterceptors } from "@nestjs/common";
import { LoggingInterceptor } from "../common/interceptors/logging.interceptor";
@Get()
@UseInterceptors(LoggingInterceptor)
findAll() {}
Real loyihalarda TransformInterceptor va LoggingInterceptor ni global qo'yish eng to'g'ri yondashuv. Barcha javoblar bir xil formatda bo'ladi, barcha so'rovlar loglanadi.
Exception Mapping — xatolarni ham formatlash
Oldingi darsda ExceptionFilter yozdik — xatolarni ushlash uchun. Interceptor ham xatolarni ushlashi mumkin. Farqi: Interceptor javobni ham, xatoni ham bitta joyda boshqaradi.
// common/interceptors/error.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
HttpException,
HttpStatus,
} from "@nestjs/common";
import { Observable, throwError } from "rxjs";
import { catchError } from "rxjs/operators";
@Injectable()
export class ErrorInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError((error) => {
// HttpException bo'lsa — uning ma'lumotini olamiz
if (error instanceof HttpException) {
return throwError(() => error);
}
// Kutilmagan xato bo'lsa — 500 qaytaramiz
return throwError(
() =>
new HttpException(
{
success: false,
message: "Ichki server xatosi",
statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
},
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
}),
);
}
}
catchError() — javob o'rniga xato kelsa ishga tushadi. throwError() — xatoni qayta otadi, lekin endi formatlangan holda.
Amaliy misol — so'rov vaqtini o'lchash
Real loyihalarda ba'zi endpointlar sekin ishlaydi. Qaysi endpoint qancha vaqt olayotganini bilish uchun:
// common/interceptors/performance.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";
@Injectable()
export class PerformanceInterceptor implements NestInterceptor {
private readonly logger = new Logger("Performance");
private readonly SLOW_REQUEST_THRESHOLD = 1000; // 1 sekund
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { method, url } = request;
const start = Date.now();
return next.handle().pipe(
tap(() => {
const duration = Date.now() - start;
// 1 sekunddan ko'p vaqt olsa — ogohlantirish
if (duration > this.SLOW_REQUEST_THRESHOLD) {
this.logger.warn(`⚠️ Sekin so'rov: ${method} ${url} — ${duration}ms`);
} else {
this.logger.log(`✓ ${method} ${url} — ${duration}ms`);
}
}),
);
}
}
Caching Interceptor — keshlashtirish
Ba'zi ma'lumotlar tez-tez o'zgarmaydi — masalan, kategoriyalar ro'yxati, davlatlar ro'yxati. Har safar ma'lumotlar bazasiga murojaat qilish — resurs isrof. Kesh — bir marta olasiz, vaqtincha saqlab qo'yasiz:
// common/interceptors/cache.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from "@nestjs/common";
import { Observable, of } from "rxjs";
import { tap } from "rxjs/operators";
@Injectable()
export class CacheInterceptor implements NestInterceptor {
// Oddiy xotira keshi — real loyihalarda Redis ishlatiladi
private cache = new Map<string, { data: any; expiry: number }>();
private readonly TTL = 60 * 1000; // 1 daqiqa
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// Faqat GET so'rovlarini keshamiz
const request = context.switchToHttp().getRequest();
if (request.method !== "GET") {
return next.handle();
}
const cacheKey = request.url;
const cached = this.cache.get(cacheKey);
// Keshda bor va muddati o'tmagan bo'lsa — keshdan qaytaramiz
if (cached && cached.expiry > Date.now()) {
return of(cached.data);
}
// Keshda yo'q — Controller dan olamiz va keshamiz
return next.handle().pipe(
tap((data) => {
this.cache.set(cacheKey, {
data,
expiry: Date.now() + this.TTL,
});
}),
);
}
}
of(cached.data) — bu RxJS dan. Mavjud qiymatdan Observable yaratadi. Ya'ni Controller ga umuman murojaat qilmasdan — keshdan javob qaytaradi.
Interceptor lar ketma-ketligi
Bir nechta Interceptor bo'lganda ular ichma-ich ishlaydi — xuddi piyoz qobiqlari kabi:
So'rov → LoggingInterceptor → TransformInterceptor → Controller
Javob ← LoggingInterceptor ← TransformInterceptor ← Controller
TransformInterceptor javobni formatlaydi. Keyin LoggingInterceptor o'sha formatlangan javobni loglaydi. Tartib muhim — main.ts da qaysi birinchi yozilsa, so'rovda birinchi, javobda oxirgi ishlaydi.
Xulosa
Interceptor — NestJS da eng kuchli mexanizmlardan biri. U so'rov va javobning ikki tomonida ham turadi. Bir martalik sozlama bilan butun ilovaning javob formatini, loglashini, keshini boshqarish mumkin.
Eslab qoling:
NestInterceptor— interfeys,interceptmetodini yozish shartnext.handle()— Controller ga o'tkazadi, Observable qaytaraditap()— javobni o'zgartirmaydi, faqat ko'radimap()— javobni o'zgartiradicatchError()— xatolarni ushlaydiof()— mavjud qiymatdan Observable yaratadi- Global Interceptor —
main.tsda bir marta, hamma joyga ta'sir qiladi - Interceptor lar ichma-ich ishlaydi — tartib muhim
Keyingi dars
11-dars: Custom Decorators