NestJS Mülakatı: Guards, Interceptors ve Modüler Mimari

Guards, Interceptors ve modüler mimari hakkında NestJS teknik mülakatlarında sıkça sorulan sorular, somut TypeScript kod örnekleri ve teknik açıklamalarla.

Teknik mülakatlar için NestJS Guards, Interceptors ve modüler mimari

NestJS teknik mülakatları her zaman framework'ün üç temel sütununa odaklanır: Guards, Interceptors ve modüler mimari. Bu mekanizmalar, iyi yapılandırılmış her NestJS uygulamasının omurgasını oluşturur ve işe alım yöneticileri framework'e gerçek hakimiyeti değerlendirmek için bunları kullanır.

Mülakatçıların değerlendirdiği şey

Guard erişimi kontrol eder (kim girer), Interceptor veriyi dönüştürür (ne girer ve ne çıkar), modüler mimari ise her şeyi düzenler. Üçüne de hakim olmak temel CRUD işlemlerinin ötesinde derin bir NestJS anlayışını gösterir.

NestJS Guards nasıl çalışır ve sık karşılaşılan mülakat senaryoları

Guards, CanActivate arayüzünü uygular ve bir isteğin handler'a ulaşıp ulaşmayacağını belirler. Middleware'den farklı olarak Guards, NestJS yürütme bağlamına (ExecutionContext) erişerek handler metaverilerine dayalı yetkilendirme kararları almayı mümkün kılar.

Klasik soru: "Kullanıcı rollerini doğrulayan bir Guard yazın."

roles.guard.tstypescript
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    // Retrieve roles defined via the @Roles() decorator
    const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
      context.getHandler(),
      context.getClass(),
    ]);

    // No roles required means the route is public
    if (!requiredRoles) return true;

    // Extract user from the HTTP request
    const { user } = context.switchToHttp().getRequest();

    // Check if the user holds at least one required role
    return requiredRoles.some((role) => user.roles?.includes(role));
  }
}

Reflector, özel dekoratörler tarafından eklenen metaverileri okur. Eşlik eden @Roles() dekoratörü şu şekildedir:

roles.decorator.tstypescript
import { SetMetadata } from '@nestjs/common';

// Creates a decorator that attaches roles as metadata
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

Güçlü bir mülakat cevabı koddan fazlasını içerir. Yürütme sırası önemlidir: Middleware → Guards → Interceptors (öncesi) → Pipes → Handler → Interceptors (sonrası) → Exception Filters. Bu sıralama neredeyse her NestJS mülakatında karşımıza çıkar.

NestJS Interceptors: istek ve yanıtları dönüştürme

Interceptors, NestInterceptor'ı uygular ve handler öncesinde ve sonrasında veri akışını manipüle etmek için RxJS kullanır. Güçlerini, yürütme hattını temsil eden CallHandler'a erişimden alırlar.

Sık sorulan soru: "Yürütme süresini ölçen bir logging Interceptor'ı oluşturun."

logging.interceptor.tstypescript
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(LoggingInterceptor.name);

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const { method, url } = request;
    const now = Date.now();

    // next.handle() invokes the route handler
    return next.handle().pipe(
      tap(() => {
        // Runs AFTER the handler responds
        const duration = Date.now() - now;
        this.logger.log(`${method} ${url}${duration}ms`);
      }),
    );
  }
}

RxJS'in tap operatörü, akışı değiştirmeden gözlemler. Yanıtı dönüştürmek için doğru operatör map'tir:

transform.interceptor.tstypescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

// Generic interface for a standardized API response
interface ApiResponse<T> {
  data: T;
  timestamp: string;
  path: string;
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<ApiResponse<T>> {
    const request = context.switchToHttp().getRequest();

    return next.handle().pipe(
      map((data) => ({
        data,
        timestamp: new Date().toISOString(),
        path: request.url,
      })),
    );
  }
}

Bu standart yanıt deseni mülakatlarda iyi puan toplar: API tasarımı ve yanıt tutarlılığı üzerine düşünme yeteneğini gösterir.

Guard vs Interceptor vs Middleware

Middleware: ham HTTP işleme (Express gibi). Guard: erişim için boolean karar. Interceptor: RxJS ile istek/yanıt akışının dönüştürülmesi. Bu üç kavramı mülakatta karıştırmak anlık bir kırmızı bayraktır.

NestJS modüler mimarisi: ölçeklenebilir uygulamalar inşa etmek

Modüler mimari, mülakatlarda junior'ları senior'lardan ayırır. NestJS modül tabanlı bir organizasyon dayatır, ancak gerçek sorular mimari kararları hedef alır: ne zaman modül oluşturulur ve modüller arası bağımlılıklar nasıl yönetilir.

İleri seviye soru: "Doğru şekilde ayrıştırılmış modüllerle bir NestJS e-ticaret uygulamasını nasıl yapılandırırsınız?"

orders/orders.module.tstypescript
import { Module } from '@nestjs/common';
import { OrdersService } from './orders.service';
import { OrdersController } from './orders.controller';
import { PaymentsModule } from '../payments/payments.module';
import { ProductsModule } from '../products/products.module';

@Module({
  // Import modules that OrdersModule depends on
  imports: [PaymentsModule, ProductsModule],
  controllers: [OrdersController],
  providers: [OrdersService],
  // Expose OrdersService to modules that import OrdersModule
  exports: [OrdersService],
})
export class OrdersModule {}

Klasik mülakat tuzağı: döngüsel bağımlılıklar. OrdersModule, PaymentsModule'ü ve PaymentsModule da OrdersModule'ü içe aktarırsa, NestJS başlangıçta hata verir. Acil çıkış forwardRef kullanır:

typescript
// Resolving circular dependencies
@Module({
  imports: [forwardRef(() => PaymentsModule)],
})
export class OrdersModule {}

En güçlü mülakat cevabı, forwardRef'in bir geçici çözüm olduğunu, gerçek bir çözüm olmadığını açıklar. Doğru düzeltme, paylaşılan bir modül (SharedOrderPaymentModule) çıkarmak için yeniden düzenlemek veya iletişimi ayrıştırmak için Event Emitter kullanmaktır.

Node.js / NestJS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Dynamic Modules ve gelişmiş yapılandırma

Dynamic Modules, projeler arasında yeniden kullanılabilir, yapılandırılabilir modüller oluşturur. Bu desen senior seviye mülakatlarda görülür.

Soru: "Dinamik seçeneklerle yapılandırılabilir bir cache modülü oluşturun."

cache/cache.module.tstypescript
import { Module, DynamicModule, Global } from '@nestjs/common';
import { CacheService } from './cache.service';

// Module configuration interface
export interface CacheModuleOptions {
  ttl: number;        // Time-to-live in seconds
  maxItems: number;   // Maximum number of entries
  prefix: string;     // Key prefix
}

@Global() // Available everywhere without explicit import
@Module({})
export class CacheModule {
  static forRoot(options: CacheModuleOptions): DynamicModule {
    return {
      module: CacheModule,
      providers: [
        {
          // Inject options via a custom token
          provide: 'CACHE_OPTIONS',
          useValue: options,
        },
        CacheService,
      ],
      exports: [CacheService],
    };
  }
}

İlgili servis seçenekleri @Inject() ile enjekte eder:

cache/cache.service.tstypescript
import { Injectable, Inject } from '@nestjs/common';
import { CacheModuleOptions } from './cache.module';

@Injectable()
export class CacheService {
  private store = new Map<string, { value: any; expiry: number }>();

  constructor(@Inject('CACHE_OPTIONS') private options: CacheModuleOptions) {}

  set(key: string, value: any): void {
    const prefixedKey = `${this.options.prefix}:${key}`;
    this.store.set(prefixedKey, {
      value,
      expiry: Date.now() + this.options.ttl * 1000,
    });
  }

  get<T>(key: string): T | null {
    const prefixedKey = `${this.options.prefix}:${key}`;
    const entry = this.store.get(prefixedKey);
    // Check expiration before returning
    if (!entry || entry.expiry < Date.now()) return null;
    return entry.value as T;
  }
}

forRoot / forRootAsync deseni bir NestJS klasiğidir. forRootAsync, yapılandırmayı enjekte edilebilir bir ConfigService'ten yükler ve bu üretim için önerilen uygulamadır.

Mülakat tuzağı: @Global()

Bir modülü @Global() olarak işaretlemek pratik görünür, ancak bunu aşırı kullanmak görünmez bağlantılar yaratır. Mülakatta @Global()'ın yatay servisler için (config, cache, logger) ayrılması gerektiğini belirtmek mimari olgunluğu gösterir.

Özel dekoratörler ve Guard kompozisyonu

Özel dekoratörler birden fazla Guard'ı ve metaveriyi tek bir notasyonda birleştirir. Bu konu mid-senior pozisyonlarda ortaya çıkar.

auth.decorator.tstypescript
import { applyDecorators, UseGuards, SetMetadata } from '@nestjs/common';
import { JwtAuthGuard } from './jwt-auth.guard';
import { RolesGuard } from './roles.guard';

// Combines JWT authentication + role verification
export function Auth(...roles: string[]) {
  return applyDecorators(
    SetMetadata('roles', roles),
    UseGuards(JwtAuthGuard, RolesGuard),
  );
}

Bir controller'da kullanım:

orders/orders.controller.tstypescript
import { Controller, Get, Post, Body } from '@nestjs/common';
import { Auth } from '../auth/auth.decorator';
import { OrdersService } from './orders.service';

@Controller('orders')
export class OrdersController {
  constructor(private ordersService: OrdersService) {}

  @Get()
  @Auth('admin', 'manager') // Single decorator replaces UseGuards + Roles
  findAll() {
    return this.ordersService.findAll();
  }

  @Post()
  @Auth('admin')
  create(@Body() dto: CreateOrderDto) {
    return this.ordersService.create(dto);
  }
}

applyDecorators deseni controller kodunu basitleştirir ve yetkilendirme mantığını merkezileştirir. Bu yaklaşımı bir mülakatta proaktif olarak önermek güçlü bir sinyaldir.

Sonuç

  • Guards, handler metaverilerine dayalı erişim kararları için CanActivate ve Reflector kullanır
  • Interceptors, akışı handler öncesinde ve sonrasında dönüştürmek için RxJS'i (tap, map) kullanır
  • Yürütme sırası Middleware → Guards → Interceptors → Pipes → Handler neredeyse evrensel bir sorudur
  • Döngüsel bağımlılıklar forwardRef ile değil, mimari yeniden düzenleme ile çözülmelidir
  • Dynamic Modules (forRoot/forRootAsync) yeniden kullanılabilir bileşenler inşa etme yeteneğini gösterir
  • applyDecorators birden fazla Guard'ı tek, okunabilir bir dekoratörde birleştirir
  • Bu soruları NestJS modülleri ve Interceptors üzerinde gerçek kodla pratik edin

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Etiketler

#nestjs
#guards
#interceptors
#architecture
#interview

Paylaş

İlgili makaleler