<link rel="stylesheet" href="/assets/fonts/jetbrains-mono/jetbrains-mono.css" />
All posts

Krijimi i një REST API të plotë me Nestjs: Udhëzuesi përfundimtar për Typeorm, JWT, RBAC dhe sigurinë

Hyrje

NestJS sot është framework-u backend Node.js më i përdorur për ndërtimin e aplikacioneve server-side të shkallëzueshme, të tipizuara dhe të mirëmbajtshme. Kombinon konceptet më solide të inxhinierisë së softuerit — Dependency Injection, module, arkitekturë me shtresa — me produktivitetin e TypeScript dhe një ekosistem që mbulon pothuajse çdo nevojë: REST, GraphQL, WebSocket, mikroshërbime, CLI, cron job.

Në këtë udhëzues ndërtojmë, hap pas hapi, një REST API të plotë dhe gati për prodhim: një API Blog me autentikim JWT, refresh token, kontroll aksesi bazuar në role (RBAC), validim, ngarkim skedarësh, logging të centralizuar dhe menaxhim të strukturuar të gabimeve — pikërisht stack-u pas shumicës së API-ve reale NestJS në 2026.

Çfarë është NestJS

NestJS është një framework opinionated i ndërtuar mbi Express (ose, alternativisht, Fastify) që imponon një strukturë të saktë në aplikacion: çdo funksionalitet organizohet në module, çdo modul ekspozon controller (menaxhojnë kërkesat HTTP) dhe provider (përmbajnë logjikën e biznesit, tipikisht service), të lidhur mes tyre përmes një sistemi Dependency Injection të integruar dhe të frymëzuar nga Angular.

Përse ta përdorësh

  • TypeScript nativ: tipizim end-to-end, autocompletim, refactoring i sigurt.
  • Arkitekturë e imponuar: ndryshe nga Express i pastër, NestJS detyron ndarjen e përgjegjësive (controller/service/repository), duke reduktuar "big ball of mud"-in tipik të projekteve Node të rritura pa strukturë.
  • Dependency Injection e integruar: testueshmëri e lartë, lidhje e ulët (low coupling), provider të ndërrueshëm (të dobishëm për mock në teste).
  • Ekosistem i pjekur: module zyrtare për TypeORM, Prisma, Mongoose, Passport, GraphQL, WebSocket, Bull/BullMQ, Swagger, gRPC, mikroshërbime.
  • Dekoratorë deklarativë: guard, interceptor, pipe dhe exception filter lejojnë cross-cutting concerns (auth, logging, validim) pa ndotur logjikën e biznesit.

Kur ia vlen (dhe kur jo)

NestJS ia vlen kur projekti ka një kompleksitet që justifikon një strukturë rigjide: ekipe me disa zhvillues, API të destinuara të rriten me kalimin e kohës, nevojë për testueshmëri të lartë, aplikacione enterprise me kërkesa sigurie dhe compliance. Është ndoshta e tepërt për një script one-off ose një prototip që do të hidhet pas një jave — atje Express i pastër ose Fastify "i zhveshur" mbeten më të shpejtë për t'u nisur.

Përparësi dhe Disavantazhe

PërparësiDisavantazhe
Strukturë e qartë dhe konsistente mes projekteve të ndryshmeKurbë mësimi më e thepisur se Express i pastër (dekoratorë, DI, module)
Testueshmëri native falë DIOverhead boilerplate për projekte shumë të vogla
Ekosistem zyrtar i gjerë dhe i mirëmbajturMë shumë "magji" (dekoratorë, reflection) krahasuar me kod eksplicit
Integrim i shkëlqyer me TypeScript dhe Swagger/OpenAPIBundle size dhe cold start pak më të lartë se framework-et minimale (relevant në mjedise serverless)
Lehtëson adoptimin e Clean Architecture / hexagonalKërkon disiplinë ekipi për të mos abuzuar me fleksibilitetin e moduleve

Raste Reale

NestJS përdoret në prodhim nga kompani si Adidas, Roche, Autodesk dhe Decathlon, përveçse është një zgjedhje shumë e zakonshme për backend SaaS B2B, platforma e-commerce, sisteme menaxhimi përdoruesish dhe API mikroshërbimesh që duhet të komunikojnë mes tyre përmes gRPC ose kuando mesazhesh (RabbitMQ, Kafka).

Parakushtet

Para se të fillosh, sigurohu që i ke të instaluara këto mjete dhe njeh bazat e TypeScript (interface, dekoratorë, generics) dhe konceptet REST (verbet HTTP, status code, idempotencë).

MjetiVersioni i rekomanduarPër çfarë shërben
Node.js20.x LTS ose më lartRuntime JavaScript mbi të cilin ekzekutohet NestJS
npm10.x (i përfshirë në Node 20)Menaxhimi i paketave
NestJS CLI@nestjs/cli 10.x+Scaffolding i moduleve, controllerëve, serviceve
TypeScript5.xGjuha në të cilën është shkruar NestJS dhe app-i yt
VS CodeVersioni i fundit stabëlEditor me mbështetje native TypeScript
PostgreSQL15.x ose më lartDatabazë relacionale për shembullin me TypeORM
Docker (opsional)24.x+Nisja e PostgreSQL lokalisht pa instalim nativ
# Verifiko versionet e instaluara
node -v
npm -v

# Instalo CLI-në e NestJS globalisht
npm install -g @nestjs/cli

nest --version

Këshillë: nëse nuk dëshiron të instalosh PostgreSQL nativisht, nise me Docker: docker run --name pg-blog -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=blog_db -p 5432:5432 -d postgres:16.

Arkitektura

Para se të shkruash kod, është thelbësore të kuptosh si NestJS e drejton një kërkesë HTTP përmes komponentëve të tij kryesorë. Ja cikli i plotë jetësor i një kërkese:

Client
  │
  ▼
┌─────────────┐
│  Middleware  │  (p.sh. logger, cookie-parser — ekzekutohet para routing)
└──────┬───────┘
       ▼
┌─────────────┐
│    Guard     │  (p.sh. AuthGuard — vendos nëse kërkesa mund të vazhdojë)
└──────┬───────┘
       ▼
┌─────────────┐
│ Interceptor  │  (faza "para" — p.sh. logging, transformim input)
└──────┬───────┘
       ▼
┌─────────────┐
│     Pipe     │  (validon dhe transformon parametrat në hyrje)
└──────┬───────┘
       ▼
┌─────────────┐
│  Controller  │  (merr kërkesën, delegon te service)
└──────┬───────┘
       ▼
┌─────────────┐
│   Service    │  (logjika e biznesit, thërret repository)
└──────┬───────┘
       ▼
┌─────────────┐
│ Repository / │  (qasje në të dhëna — TypeORM, Prisma, Mongoose...)
│   Database   │
└──────┬───────┘
       ▼
┌─────────────┐
│ Interceptor  │  (faza "pas" — p.sh. transformimi i përgjigjes)
└──────┬───────┘
       ▼
┌──────────────────┐
│ Exception Filter  │  (përgjon VETËM nëse hidhet një përjashtim)
└──────┬────────────┘
       ▼
    Response

Controller

Shtresa më e jashtme: merr kërkesat HTTP, nxjerr parametra/body/query dhe delegon menjëherë logjikën te service-i përkatës. Një controller nuk duhet kurrë të përmbajë logjikë biznesi — përgjegjësia e tij e vetme është mapping HTTP ↔ thirrje metode.

Service

Përmban logjikën reale të biznesit. Është një provider i injektueshëm, tipikisht i shënuar me @Injectable(), dhe injektohet në controller (ose në service të tjerë) përmes konstruktorit.

Module

Moduli është njësia organizative e NestJS: grupon controller, provider dhe importe modulesh të tjera. Çdo aplikacion ka të paktën një AppModule rrënjë, dhe funksionalitetet tipikisht izolohen në feature module (p.sh. PostsModule, AuthModule, UsersModule).

Provider

Çdo klasë e menaxhuar nga kontejneri i Dependency Injection i Nest: service, repository, factory, helper. Deklarohet te providers në modul dhe mund të injektohet kudo ku është i disponueshëm (në të njëjtin modul, ose i eksportuar drejt moduleve të tjera).

Middleware

Funksione të ekzekutuara para routing-ut të Nest, me qasje direkte te req, res dhe next() — i njëjti model si Express. I dobishëm për logging të papërpunuar, parsim cookie, ose header custom të aplikuar globalisht.

Guard

Vendosin nëse një kërkesë mund të vazhdojë, duke kthyer true/ false (ose duke hedhur një përjashtim). Janë vendi i saktë për autentikim dhe autorizim — kurrë brenda controller-it apo service-it.

Interceptor

Pozicionohen rreth ekzekutimit të handler-it të rrotës (si middleware AOP): mund të transformojnë kërkesën para se të arrijë te controller dhe përgjigjen para se të dalë. Raste përdorimi tipike: logging i kohëve të përgjigjes, transformim uniform i përgjigjeve, caching, menaxhim timeout-esh.

Pipe

Transformojnë dhe validojnë të dhënat në hyrje (parametra rrote, query string, body) para se të arrijnë te controller. ValidationPipe, i integruar me class-validator, është pipe-i më i përdorur në çdo API serioze NestJS.

Exception Filter

Përgjojnë përjashtimet e hedhura kudo në ciklin e kërkesës dhe i shndërrojnë në një përgjigje HTTP konsistente (status code, trup JSON i strukturuar), duke shmangur që stack trace apo gabime të papërpunuara të arrijnë te klienti.

Instalimi

# 1. Krijo një projekt të ri NestJS
nest new blog-api

# Gjatë krijimit, zgjidh npm si package manager kur kërkohet

cd blog-api

# 2. Instalo varësitë për databazën (TypeORM + driver PostgreSQL)
npm install @nestjs/typeorm typeorm pg

# 3. Instalo varësitë për validimin
npm install class-validator class-transformer

# 4. Instalo varësitë për autentikimin JWT
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
npm install --save-dev @types/passport-jwt @types/bcrypt

# 5. Instalo varësitë për sigurinë dhe rate limiting
npm install helmet @nestjs/throttler

# 6. Instalo varësitë për ngarkimin e skedarëve
npm install @nestjs/platform-express multer
npm install --save-dev @types/multer

# 7. Instalo Swagger për dokumentimin automatik të API-së
npm install @nestjs/swagger

# 8. Instalo logger-in e strukturuar
npm install nestjs-pino pino-http pino-pretty

# 9. Konfiguro variablat e mjedisit
npm install @nestjs/config

Çdo komandë instalon një bllok funksional të saktë: @nestjs/typeorm + typeorm + pg lidhin Nest me PostgreSQL përmes ORM-it TypeORM; class-validator/class-transformer aktivizojnë DTO të validuar automatikisht; stack-u passport/passport-jwt/bcrypt ndërton të gjithë flukun e autentikimit; helmet dhe @nestjs/throttler forcojnë sigurinë HTTP dhe rate limiting-un; multer menaxhon multipart/form-data për ngarkimet; nestjs-pino ofron logging të strukturuar JSON, i përshtatshëm për prodhimin.

Implementimi Hap-pas-Hapi

1. Struktura e dosjeve

src/
├── main.ts
├── app.module.ts
├── config/
│   └── configuration.ts
├── common/
│   ├── filters/
│   │   └── http-exception.filter.ts
│   ├── interceptors/
│   │   ├── logging.interceptor.ts
│   │   └── transform.interceptor.ts
│   ├── guards/
│   │   └── roles.guard.ts
│   ├── decorators/
│   │   └── roles.decorator.ts
│   └── pipes/
│       └── parse-object-id.pipe.ts
├── auth/
│   ├── auth.module.ts
│   ├── auth.controller.ts
│   ├── auth.service.ts
│   ├── strategies/
│   │   ├── jwt.strategy.ts
│   │   └── jwt-refresh.strategy.ts
│   └── dto/
│       ├── login.dto.ts
│       └── register.dto.ts
├── users/
│   ├── users.module.ts
│   ├── users.service.ts
│   └── entities/
│       └── user.entity.ts
└── posts/
    ├── posts.module.ts
    ├── posts.controller.ts
    ├── posts.service.ts
    ├── entities/
    │   └── post.entity.ts
    └── dto/
        ├── create-post.dto.ts
        └── update-post.dto.ts

2. Konfigurim i centralizuar me @nestjs/config

// src/config/configuration.ts
export default () => ({
  port: parseInt(process.env.PORT ?? '3000', 10),
  database: {
    host: process.env.DB_HOST ?? 'localhost',
    port: parseInt(process.env.DB_PORT ?? '5432', 10),
    username: process.env.DB_USERNAME ?? 'postgres',
    password: process.env.DB_PASSWORD ?? 'postgres',
    name: process.env.DB_NAME ?? 'blog_db',
  },
  jwt: {
    accessSecret: process.env.JWT_ACCESS_SECRET ?? 'change-me-access',
    accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN ?? '15m',
    refreshSecret: process.env.JWT_REFRESH_SECRET ?? 'change-me-refresh',
    refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN ?? '7d',
  },
});
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ThrottlerModule } from '@nestjs/throttler';
import configuration from './config/configuration';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
import { PostsModule } from './posts/posts.module';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true, load: [configuration] }),
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (cfg: ConfigService) => ({
        type: 'postgres',
        host: cfg.get('database.host'),
        port: cfg.get('database.port'),
        username: cfg.get('database.username'),
        password: cfg.get('database.password'),
        database: cfg.get('database.name'),
        autoLoadEntities: true,
        synchronize: process.env.NODE_ENV !== 'production', // ⚠️ vetëm në zhvillim
      }),
    }),
    ThrottlerModule.forRoot([{ ttl: 60000, limit: 100 }]),
    AuthModule,
    UsersModule,
    PostsModule,
  ],
})
export class AppModule {}

Rresht për rresht: ConfigModule.forRoot({ isGlobal: true }) e bën ConfigService të disponueshëm në të gjithë aplikacionin pa pasur nevojë të riimportosh modulin kudo. TypeOrmModule.forRootAsync ndërton lidhjen me databazën në mënyrë asinkrone, duke lexuar vlerat nga ConfigService në vend të një objekti statik — e nevojshme për të përdorur variabla mjedisi. synchronize: true bën që TypeORM të krijojë/përditësojë automatikisht tabelat bazuar në entity: shumë e volitshme në zhvillim, e rrezikshme në prodhim (mund të fshijë të dhëna), ku do të përdoren në vend të kësaj migration.

3. Entity me TypeORM

// src/users/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, OneToMany } from 'typeorm';
import { Post } from '../../posts/entities/post.entity';
import { Exclude } from 'class-transformer';

export enum UserRole {
  ADMIN = 'admin',
  AUTHOR = 'author',
  READER = 'reader',
}

@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  email: string;

  @Column()
  @Exclude() // përjashton hash-in e fjalëkalimit nga përgjigjet e serializuara
  passwordHash: string;

  @Column({ type: 'enum', enum: UserRole, default: UserRole.READER })
  role: UserRole;

  @OneToMany(() => Post, (post) => post.author)
  posts: Post[];

  @CreateDateColumn()
  createdAt: Date;
}
// src/posts/entities/post.entity.ts
import {
  Entity, Column, PrimaryGeneratedColumn, ManyToOne,
  CreateDateColumn, UpdateDateColumn, Index,
} from 'typeorm';
import { User } from '../../users/entities/user.entity';

@Entity('posts')
export class Post {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  title: string;

  @Index({ unique: true })
  @Column()
  slug: string;

  @Column('text')
  content: string;

  @Column({ default: false })
  published: boolean;

  @Column({ nullable: true })
  coverImageUrl?: string;

  @ManyToOne(() => User, (user) => user.posts, { eager: true })
  author: User;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}

4. DTO me class-validator

// src/posts/dto/create-post.dto.ts
import { IsString, IsBoolean, IsOptional, MinLength, MaxLength } from 'class-validator';

export class CreatePostDto {
  @IsString()
  @MinLength(5)
  @MaxLength(200)
  title: string;

  @IsString()
  @MinLength(20)
  content: string;

  @IsBoolean()
  @IsOptional()
  published?: boolean;
}

// src/posts/dto/update-post.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreatePostDto } from './create-post.dto';

export class UpdatePostDto extends PartialType(CreatePostDto) {}

PartialType gjeneron automatikisht një version ku të gjitha fushat e DTO-së origjinale bëhen opsionale — perfekte për update-e të pjesshme (PATCH), pa dublikuar dekoratorët e validimit.

5. Aktivizimi global i ValidationPipe

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import helmet from 'helmet';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.use(helmet());
  app.enableCors({ origin: process.env.CORS_ORIGIN?.split(',') ?? '*' });

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,           // heq propertitë që nuk janë në DTO
      forbidNonWhitelisted: true, // hedh gabim nëse vijnë propertitë extra
      transform: true,            // konverton automatikisht tipet (p.sh. string → number)
    }),
  );

  app.useGlobalFilters(new HttpExceptionFilter());
  app.setGlobalPrefix('api/v1');

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

whitelist: true + forbidNonWhitelisted: true janë çifti më i rëndësishëm për sigurinë e DTO-ve: pa të, një klient mund të dërgojë fusha extra (p.sh. role: 'admin' në një kërkesë regjistrimi) që TypeORM mund t'i ruajë pa dashje nëse kodi nuk i filtron eksplicitisht diku tjetër.

6. Middleware custom

// src/common/middleware/request-id.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { randomUUID } from 'crypto';

@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction): void {
    req['requestId'] = randomUUID();
    res.setHeader('X-Request-Id', req['requestId']);
    next();
  }
}

// regjistrimi në app.module.ts
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(RequestIdMiddleware).forRoutes('*');
  }
}

7. Guard: JwtAuthGuard dhe RolesGuard

// src/auth/strategies/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(config: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: config.get('jwt.accessSecret'),
    });
  }

  async validate(payload: { sub: string; email: string; role: string }) {
    // Vlera e kthyer i bashkëngjitet req.user
    return { userId: payload.sub, email: payload.email, role: payload.role };
  }
}
// src/common/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
import { UserRole } from '../../users/entities/user.entity';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles);

// src/common/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
import { UserRole } from '../../users/entities/user.entity';

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

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!requiredRoles) return true; // asnjë kufizim roli në rrotë

    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.includes(user?.role);
  }
}

Modeli Reflector.getAllAndOverride lexon metadatat e vendosura nga dekoratori @Roles(...) si në nivel metode të vetme ashtu edhe klase të tërë, duke lejuar përcaktimin e një kufizimi roli mbi një controller të tërë dhe mbivendosjen e tij në rrota individuale kur nevojitet.

8. Interceptor: logging dhe transformimi i përgjigjes

// src/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('HTTP');

  intercept(context: ExecutionContext, next: CallHandler): Observable {
    const req = context.switchToHttp().getRequest();
    const start = Date.now();

    return next.handle().pipe(
      tap(() => {
        const ms = Date.now() - start;
        this.logger.log(`${req.method} ${req.url} — ${ms}ms`);
      }),
    );
  }
}

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

export interface Response {
  success: true;
  data: T;
  timestamp: string;
}

@Injectable()
export class TransformInterceptor implements NestInterceptor> {
  intercept(context: ExecutionContext, next: CallHandler): Observable> {
    return next.handle().pipe(
      map((data) => ({
        success: true,
        data,
        timestamp: new Date().toISOString(),
      })),
    );
  }
}

9. Exception Filter global

// src/common/filters/http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger('ExceptionFilter');

  catch(exception: unknown, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status = exception instanceof HttpException
      ? exception.getStatus()
      : HttpStatus.INTERNAL_SERVER_ERROR;

    const message = exception instanceof HttpException
      ? exception.getResponse()
      : 'Errore interno del server';

    // Logon stack trace-n e plotë vetëm anash serverit, kurrë në përgjigjen ndaj klientit
    if (!(exception instanceof HttpException)) {
      this.logger.error(exception instanceof Error ? exception.stack : exception);
    }

    response.status(status).json({
      success: false,
      statusCode: status,
      path: request.url,
      timestamp: new Date().toISOString(),
      message,
    });
  }
}

Një detaj sigurie thelbësor: kur përjashtimi nuk është një HttpException i njohur (pra një gabim i papritur, p.sh. një bug ose një gabim databaze), mesazhi i kthyer te klienti është gjenerik ("Gabim i brendshëm i serverit") — stack trace-i real logohet vetëm anash serverit. Ekspozimi i stack trace-ve te klientët është një problem sigurie i njohur (information disclosure).

10. Pipe custom

// src/common/pipes/parse-uuid-or-404.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, NotFoundException } from '@nestjs/common';
import { isUUID } from 'class-validator';

@Injectable()
export class ParseUuidOr404Pipe implements PipeTransform {
  transform(value: string, _metadata: ArgumentMetadata): string {
    if (!isUUID(value)) {
      throw new NotFoundException(`Risorsa con id "${value}" non trovata`);
    }
    return value;
  }
}

Kthimi i një 404 në vend të një 400 Bad Request gjenerik kur id-ja nuk është as UUID i vlefshëm është një zgjedhje e qëllimshme: nga këndvështrimi i klientit, "burim jo i gjetur" është semantikisht më korrekt dhe nuk zbulon detaje mbi formatin e brendshëm të id-ve.

Shembull Real: API Blog e Plotë

Le t'i bashkojmë të gjitha pjesët në një modul PostsModule të plotë, me CRUD, autentikim, autorizim bazuar në role dhe ngarkim të imazhit të kopertinës.

Auth Service: login, register, refresh token

// src/auth/auth.service.ts
import { Injectable, UnauthorizedException, ConflictException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import * as bcrypt from 'bcrypt';
import { UsersService } from '../users/users.service';
import { RegisterDto } from './dto/register.dto';
import { LoginDto } from './dto/login.dto';

@Injectable()
export class AuthService {
  constructor(
    private usersService: UsersService,
    private jwtService: JwtService,
    private config: ConfigService,
  ) {}

  async register(dto: RegisterDto) {
    const existing = await this.usersService.findByEmail(dto.email);
    if (existing) throw new ConflictException('Email già registrata');

    const passwordHash = await bcrypt.hash(dto.password, 12);
    const user = await this.usersService.create({ ...dto, passwordHash });
    return this.buildTokens(user.id, user.email, user.role);
  }

  async login(dto: LoginDto) {
    const user = await this.usersService.findByEmail(dto.email);
    if (!user) throw new UnauthorizedException('Credenziali non valide');

    const passwordValid = await bcrypt.compare(dto.password, user.passwordHash);
    if (!passwordValid) throw new UnauthorizedException('Credenziali non valide');

    return this.buildTokens(user.id, user.email, user.role);
  }

  async refresh(userId: string, email: string, role: string) {
    // Në prodhim: verifiko edhe që refresh token-i është ende i vlefshëm anash DB
    // (whitelist/blacklist) për ta revokuar, p.sh. te logout.
    return this.buildTokens(userId, email, role);
  }

  private buildTokens(sub: string, email: string, role: string) {
    const payload = { sub, email, role };
    const accessToken = this.jwtService.sign(payload, {
      secret: this.config.get('jwt.accessSecret'),
      expiresIn: this.config.get('jwt.accessExpiresIn'),
    });
    const refreshToken = this.jwtService.sign(payload, {
      secret: this.config.get('jwt.refreshSecret'),
      expiresIn: this.config.get('jwt.refreshExpiresIn'),
    });
    return { accessToken, refreshToken };
  }
}

Përse dy token të veçantë? Access token-i ka jetë të shkurtër (15 minuta) dhe është ai që dërgohet me çdo kërkesë — nëse vidhet, dëmi është i kufizuar në kohë. Refresh token-i ka jetë më të gjatë (ditë) por përdoret vetëm për të marrë një access token të ri në një endpoint dedikuar, duke reduktuar sipërfaqen e sulmit.

Auth Controller

// src/auth/auth.controller.ts
import { Controller, Post, Body, UseGuards, Req } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { RegisterDto } from './dto/register.dto';
import { LoginDto } from './dto/login.dto';

@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {}

  @Post('register')
  register(@Body() dto: RegisterDto) {
    return this.authService.register(dto);
  }

  @Post('login')
  login(@Body() dto: LoginDto) {
    return this.authService.login(dto);
  }

  @UseGuards(AuthGuard('jwt-refresh'))
  @Post('refresh')
  refresh(@Req() req) {
    return this.authService.refresh(req.user.userId, req.user.email, req.user.role);
  }
}

Posts Controller: CRUD, RBAC dhe upload

// src/posts/posts.controller.ts
import {
  Controller, Get, Post, Body, Patch, Param, Delete,
  UseGuards, UseInterceptors, UploadedFile, Query,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { AuthGuard } from '@nestjs/passport';
import { PostsService } from './posts.service';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { Roles } from '../common/decorators/roles.decorator';
import { RolesGuard } from '../common/guards/roles.guard';
import { UserRole } from '../users/entities/user.entity';
import { ParseUuidOr404Pipe } from '../common/pipes/parse-uuid-or-404.pipe';
import { CurrentUser } from '../common/decorators/current-user.decorator';

@Controller('posts')
export class PostsController {
  constructor(private postsService: PostsService) {}

  @Get()
  findAll(@Query('page') page = 1, @Query('limit') limit = 10) {
    return this.postsService.findAll(Number(page), Number(limit));
  }

  @Get(':id')
  findOne(@Param('id', ParseUuidOr404Pipe) id: string) {
    return this.postsService.findOne(id);
  }

  @UseGuards(AuthGuard('jwt'), RolesGuard)
  @Roles(UserRole.AUTHOR, UserRole.ADMIN)
  @Post()
  create(@Body() dto: CreatePostDto, @CurrentUser() user) {
    return this.postsService.create(dto, user.userId);
  }

  @UseGuards(AuthGuard('jwt'), RolesGuard)
  @Roles(UserRole.AUTHOR, UserRole.ADMIN)
  @Patch(':id')
  update(@Param('id', ParseUuidOr404Pipe) id: string, @Body() dto: UpdatePostDto, @CurrentUser() user) {
    return this.postsService.update(id, dto, user.userId, user.role);
  }

  @UseGuards(AuthGuard('jwt'), RolesGuard)
  @Roles(UserRole.ADMIN)
  @Delete(':id')
  remove(@Param('id', ParseUuidOr404Pipe) id: string) {
    return this.postsService.remove(id);
  }

  @UseGuards(AuthGuard('jwt'))
  @Post(':id/cover')
  @UseInterceptors(FileInterceptor('file', {
    limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
    fileFilter: (_req, file, cb) => {
      const allowed = ['image/jpeg', 'image/png', 'image/webp'];
      cb(null, allowed.includes(file.mimetype));
    },
  }))
  uploadCover(@Param('id', ParseUuidOr404Pipe) id: string, @UploadedFile() file: Express.Multer.File) {
    return this.postsService.setCoverImage(id, file);
  }
}

Vër re si çdo rrotë aplikon vetëm guard-et dhe rolet rreptësisht të nevojshme: findAll/findOne janë publike (asnjë guard), create/update kërkojnë rol author ose admin, ndërsa remove është e rezervuar vetëm për admin — një shembull konkret i parimit të privilegjit minimal të aplikuar rrotë për rrotë.

Posts Service

// src/posts/posts.service.ts
import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Post } from './entities/post.entity';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { UserRole } from '../users/entities/user.entity';
import slugify from 'slugify';

@Injectable()
export class PostsService {
  constructor(@InjectRepository(Post) private postsRepo: Repository) {}

  async findAll(page: number, limit: number) {
    const [items, total] = await this.postsRepo.findAndCount({
      where: { published: true },
      order: { createdAt: 'DESC' },
      skip: (page - 1) * limit,
      take: limit,
    });
    return { items, total, page, limit };
  }

  async findOne(id: string): Promise {
    const post = await this.postsRepo.findOne({ where: { id } });
    if (!post) throw new NotFoundException(`Post ${id} non trovato`);
    return post;
  }

  async create(dto: CreatePostDto, authorId: string): Promise {
    const post = this.postsRepo.create({
      ...dto,
      slug: slugify(dto.title, { lower: true, strict: true }),
      author: { id: authorId } as any,
    });
    return this.postsRepo.save(post);
  }

  async update(id: string, dto: UpdatePostDto, userId: string, userRole: UserRole): Promise {
    const post = await this.findOne(id);

    // Një author mund të modifikojë vetëm postimet e veta; admin mund t'i modifikojë të gjitha
    if (userRole !== UserRole.ADMIN && post.author.id !== userId) {
      throw new ForbiddenException('Non puoi modificare un post di un altro autore');
    }

    Object.assign(post, dto);
    return this.postsRepo.save(post);
  }

  async remove(id: string): Promise {
    const result = await this.postsRepo.delete(id);
    if (result.affected === 0) throw new NotFoundException(`Post ${id} non trovato`);
  }

  async setCoverImage(id: string, file: Express.Multer.File): Promise {
    const post = await this.findOne(id);
    // Në prodhim: ngarko në S3/Cloud Storage në vend të diskut lokal
    post.coverImageUrl = `/uploads/${file.filename}`;
    return this.postsRepo.save(post);
  }
}

Vër re kontrollin userRole !== UserRole.ADMIN && post.author.id !== userId te metoda update: është një shembull autorizimi në nivel burimi (jo vetëm rrote) — një author i autentikuar me një token JWT të vlefshëm mund të bllokohet gjithsesi nëse po përpiqet të modifikojë burimin e dikujt tjetër. Ky kontroll duhet bërë gjithmonë te service-i, kurrë i deleguar vetëm te guard-i, që operon në një nivel shumë gjenerik për të njohur pronarin e burimit specifik.

Praktika më të Mira

ParimiZbatimi praktik në NestJS
Clean ArchitectureNdarje e qartë mes controller (HTTP), service (logjika e biznesit) dhe repository (persistenca). Domeni nuk duhet të varet kurrë nga detaje infrastrukturore si Express apo TypeORM.
SOLID — Single ResponsibilityNjë controller mapon kërkesa HTTP, një service përmban një zonë të vetme logjike biznesi, një repository një aggregate të vetëm.
SOLID — Dependency InversionService-t varen nga interface/token abstrakte (p.sh. @InjectRepository), jo nga implementime konkrete — të zëvendësueshëm në teste me mock.
DRYLogjikë validimi e ndarë në DTO me PartialType/PickType/OmitType, duke shmangur dublikimin mes Create dhe Update DTO.
KISSShmang futjen e CQRS, Event Sourcing apo mikroshërbimeve derisa kompleksiteti real i domenit ta justifikojë.
Repository PatternTypeORM/Prisma tashmë e ofrojnë këtë shtresë; shmang "shpimin" e abstraksionit duke thirrur query SQL të papërpunuara direkt te service-t përveç rasteve kritike të performancës.
DTOÇdo endpoint ka një DTO input dedikuar — kurrë mos ekspozo direkt entity-t e databazës si kontratë API.
ValidationValidationPipe global me whitelist dhe forbidNonWhitelisted gjithmonë aktive në çdo projekt, pa përjashtime.

Performanca

Caching

// Caching në nivel endpoint me @nestjs/cache-manager
import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager';
import { UseInterceptors } from '@nestjs/common';

@UseInterceptors(CacheInterceptor)
@CacheTTL(60) // cache për 60 sekonda
@Get()
findAll() {
  return this.postsService.findAll(1, 10);
}

Lazy Loading i moduleve

Në aplikacione monolitike të mëdha, LazyModuleLoader lejon ngarkimin e moduleve pak të përdorura (p.sh. një modul raportimi) vetëm kur nevojiten realisht, duke reduktuar kohën e bootstrap-it.

Query të optimizuara

  • Përdor select eksplicit në TypeORM për të shmangur transferimin e kolonave të panevojshme (p.sh. duke përjashtuar content në listat).
  • Shto indekse te kolonat e përdorura shpesh në WHERE/ORDER BY (shih @Index te slug-u në entity-n Post).
  • Shmang problemin N+1: përdor relations ose QueryBuilder me leftJoinAndSelect në vend të ngarkimit të relacioneve brenda loop-i.
  • Përdor gjithmonë paginim (skip/take) — kurrë mos kthe koleksione të pakufizuara.

Logging dhe Monitorim

Në prodhim, zëvendëso logger-in e parazgjedhur me nestjs-pino për log të strukturuar JSON, lehtësisht të indeksueshëm nga mjete si Grafana Loki apo Datadog. Për monitorimin e performancës, integro @nestjs/terminus për health check (/health) të përdorur nga load balancer dhe orkestratorë (Kubernetes, Railway).

Profiling

Për të gjetur pengesa reale, përdor node --prof ose mjete APM (Application Performance Monitoring) si New Relic apo Elastic APM, që gjurmojnë kohën e shpenzuar në çdo handler, query dhe thirrje të jashtme — shmang optimizimin "me ndjesi" pa të dhëna reale.

Siguria

MasaSi zbatohet
JWTAccess token me jetë të shkurtër i nënshkruar me secret robust (32+ karaktere random), kurrë hardcoded në kod.
Refresh TokenJetë më e gjatë, idealisht me mundësi revokimi (whitelist në Redis/DB), rotacion në çdo përdorim.
Hash Fjalëkalimi / bcryptbcrypt.hash(password, 12) — kurrë MD5/SHA1, kurrë fjalëkalim në tekst të thjeshtë në DB.
Helmetapp.use(helmet()) vendos header sigurie (CSP, X-Frame-Options, HSTS) me një rresht të vetëm.
CORSapp.enableCors({ origin: [...] }) me whitelist eksplicite domenesh, kurrë origin: '*' në prodhim nëse API kërkon credentials.
Rate Limiting@nestjs/throttler për të kufizuar kërkesat për IP, thelbësore te endpoint si /auth/login për të zbutur sulmet brute-force.
ValidationPipewhitelist + forbidNonWhitelisted për të bllokuar mass assignment mbi fusha të papritura.
Sanitizim Inputclass-validator për strukturën, plus sanitizim eksplicit HTML (p.sh. sanitize-html) nëse fusha lejon markup të lirë, për të parandaluar XSS.
// Shembull: rate limiting më agresiv specifik te login
import { Throttle } from '@nestjs/throttler';

@Throttle({ default: { limit: 5, ttl: 60000 } }) // maksimumi 5 tentativa në minutë për IP
@Post('login')
login(@Body() dto: LoginDto) {
  return this.authService.login(dto);
}

Gabime të Zakonshme

1. Harresa e whitelist/forbidNonWhitelisted në ValidationPipe

Problemi: një klient mund të dërgojë fusha extra jo të parashikuara nga DTO (p.sh. role: 'admin').
Shkaku: ValidationPipe i konfiguruar pa whitelist: true.
Zgjidhja: aktivizo gjithmonë whitelist dhe forbidNonWhitelisted globalisht te main.ts.

2. synchronize: true në prodhim

Problemi: TypeORM mund të ndryshojë/fshijë kolona apo tabela gjatë ekzekutimit.
Shkaku: ngatërrim mes mjedisit të zhvillimit dhe prodhimit në konfigurimin e databazës.
Zgjidhja: synchronize: false në prodhim, menaxho skemën me migration eksplicite (typeorm migration:generate).

3. Circular dependency mes moduleve

Problemi: gabim "Nest cannot resolve dependencies" ose crash i heshtur në nisje.
Shkaku: dy module importojnë njëri-tjetrin direkt.
Zgjidhja: përdor forwardRef(() => ModuleB) në të dy modulet, ose — më mirë — nxirr varësinë e përbashkët në një modul të tretë.

4. Ekspozimi i entity-ve direkt si përgjigje API

Problemi: fusha të ndjeshme (p.sh. passwordHash) përfundojnë në përgjigjen JSON.
Shkaku: controller-i kthen entity-n TypeORM ashtu siç është, pa një shtresë DTO/serializer.
Zgjidhja: përdor @Exclude() të class-transformer te entity së bashku me ClassSerializerInterceptor global, ose më mirë ende një DTO përgjigjeje eksplicit.

5. Fjalëkalime të ruajtura pa hashing

Problemi: kompromentimi i DB ekspozon të gjitha fjalëkalimet në tekst të thjeshtë.
Shkaku: lëshim (shpesh gjatë rapid prototyping) i hapit të hashing.
Zgjidhja: hash me bcrypt/argon2 gjithmonë, edhe në zhvillim — nuk ka arsye të vlefshme për ta anashkaluar kurrë.

6. Secret JWT të dobët ose hardcoded në kod

Problemi: një secret i parashikueshëm lejon falsifikimin e token-ave të vlefshëm.
Shkaku: secret i parazgjedhur i lënë në prodhim, ose i commit-uar në repository.
Zgjidhja: secret të gjeneruar rastësisht (të paktën 256 bit), të menaxhuar vetëm përmes variablave mjedisi/secret manager, kurrë në kodin burimor.

7. Mungesa e menaxhimit të gabimeve asinkrone te service-t

Problemi: promise rejection të pamenaxhuara shkaktojnë crash të procesit Node.
Shkaku: thirrje async pa try/catch ku nevojitet, ose harresa e exception filter-it global.
Zgjidhja: lejo që përjashtimet të ngjiten deri te exception filter-i global (sjellja e parazgjedhur e Nest), përgjo vetëm ku nevojitet një sjellje tjetër (fallback, retry).

8. Query N+1 të pazbuluara

Problemi: endpoint-i bëhet shumë i ngadaltë me rritjen e të dhënave.
Shkaku: ngarkim relacionesh brenda një loop-i në vend të një join-i të vetëm.
Zgjidhja: përdor relations/leftJoinAndSelect, monitoro query-t e gjeneruara me logging: true në zhvillim.

9. CORS i hapur për të gjithë në prodhim me credentials

Problemi: çdo faqe interneti mund të kryejë kërkesa të autentikuara te API në emër të përdoruesit.
Shkaku: origin: '*' i kombinuar me credentials: true.
Zgjidhja: whitelist eksplicite e domeneve të autorizuara.

10. Asnjë rate limiting te endpoint-et e autentikimit

Problemi: sulme brute-force te login/regjistrim.
Shkaku: @nestjs/throttler i paaplikuar ose me pragje shumë permisive.
Zgjidhja: @Throttle më restriktiv specifikisht te login/register/reset-password.

11. DTO Update identik me DTO Create pa PartialType

Problemi: çdo PATCH kërkon të gjitha fushat e detyrueshme, duke prishur update-t e pjesshme.
Shkaku: kopjim/ngjitje e DTO Create pa i bërë fushat opsionale.
Zgjidhja: class UpdateXDto extends PartialType(CreateXDto) {}.

12. Guard të aplikuar vetëm në nivel rrote, kurrë burimi

Problemi: një përdorues i autentikuar mund të modifikojë burime të përdoruesve të tjerë.
Shkaku: mbështetje vetëm te @UseGuards(AuthGuard('jwt')) pa kontroll ownership te service.
Zgjidhja: kontroll eksplicit resource.ownerId === user.id (ose rol admin) te service, siç tregohet në shembullin PostsService.update.

13. Lidhje me databazën pa pool të konfiguruar saktë

Problemi: shterim lidhjesh nën ngarkesë, gabime "too many clients".
Shkaku: vlera parazgjedhur pool jo të përshtatshme për trafikun real.
Zgjidhja: konfiguro eksplicitisht extra: { max: 20 } te connection e TypeORM, e kalibruar mbi planin e databazës.

14. Ngarkim skedarësh pa kufij madhësie apo tipi

Problemi: një përdorues keqdashës ngarkon skedarë të mëdhenj ose ekzekutues të maskuar si imazhe.
Shkaku: FileInterceptor i konfiguruar pa limits as fileFilter.
Zgjidhja: vendos gjithmonë limits.fileSize dhe valido mimetype-in, siç në shembullin uploadCover.

15. Variabla mjedisi të pavaliduara në nisje

Problemi: app-i niset "në heshtje" me konfigurim jo të plotë dhe dështon në mënyrë kriptike më vonë.
Shkaku: asnjë skemë validimi mbi env var.
Zgjidhja: përdor ConfigModule.forRoot({ validationSchema: Joi.object({...}) }) për ta bërë bootstrap-in të dështojë menjëherë nëse mungon një variabël kritike.

16. Logging i të dhënave të ndjeshme

Problemi: fjalëkalime, token apo të dhëna personale përfundojnë në log në tekst të thjeshtë.
Shkaku: logging i pashqyrtuar i të gjithë body-t të kërkesës.
Zgjidhja: redakto (maskove) eksplicitisht fushat e ndjeshme para logging-ut, ose përdor redact path-et e pino.

17. Teste të shkruara vetëm për "happy path"-in

Problemi: bug që shfaqen vetëm me input jo të vlefshëm apo edge case mbeten të padukshëm deri në prodhim.
Shkaku: presion kohor, mbulim testesh vetëm te fluksi kryesor.
Zgjidhja: testo eksplicitisht gabime 4xx të pritura (validim, leje, not found), jo vetëm 2xx.

18. Mungesa e versionimit të API-së

Problemi: çdo ndryshim breaking prish klientët ekzistues pa paralajmërim.
Shkaku: asnjë prefiks versioni (/api/v1) ose strategji versionimi Nest e konfiguruar.
Zgjidhja: app.setGlobalPrefix('api/v1') që nga dita e parë, edhe në projekte të vogla.

19. Varësi të papërditësuara me vulnerabilitete të njohura

Problemi: API mbetet e ekspozuar ndaj CVE të dokumentuara publikisht.
Shkaku: mungesa e një procesi periodik auditimi varësish.
Zgjidhja: npm audit i integruar në CI, përditësime të rregullta me Dependabot/Renovate.

20. Asnjë dallim mes gabimeve të validimit dhe gabimeve të domenit

Problemi: të gjitha gabimet kthehen si 500 gjenerik, duke e bërë të pamundur për klientin të dallojë një input të gabuar nga një problem serveri.
Shkaku: përdorim Error gjenerik në vend të klasave specifike HttpException të Nest.
Zgjidhja: përdor gjithmonë përjashtimet e tipizuara saktë (BadRequestException, NotFoundException, ForbiddenException, ConflictException...) — kështu Nest komunikon status code-in e saktë HTTP.

Pyetje të Shpeshta (FAQ)

1. A është NestJS i përshtatshëm edhe për projekte të vogla?

Po, por vlera reale shfaqet kur projekti rritet: në një script të vogël, struktura mund të duket e tepërt krahasuar me Express të pastër.

2. A duhet të përdor detyrimisht TypeScript?

Teknikisht NestJS mbështet edhe JavaScript të pastër, por humbet pjesa më e madhe e vlerës (dekoratorë të tipizuar, DI type-safe). Këshillohet fuqimisht të mos përdoret në prodhim.

3. TypeORM apo Prisma?

TypeORM integrohet më nativisht me dekoratorët NestJS dhe është më i pjekur në ekosistemin Nest; Prisma ofron një client të gjeneruar më type-safe dhe një developer experience më të mirë në migration, por kërkon një shtresë integrimi manuale me Nest.

4. Si i menaxhoj migration-et e databazës?

Me TypeORM: typeorm migration:generate për t'i gjeneruar nga diff-i i entity-ve, typeorm migration:run për t'i aplikuar në CI/CD, kurrë synchronize: true në prodhim.

5. Si strukturoj një projekt shumë të madh?

Modularizo sipas domenit (feature module), jo sipas tipit teknik — shmang dosje globale "controllers/", "services/" që përzien domene të ndryshme.

6. Si bëj testing të controllerëve?

Me Test.createTestingModule@nestjs/testing, duke mockuar service-t e injektuar; për service-t, mocko repository-t TypeORM me getRepositoryToken.

7. Cili është dallimi mes Guard dhe Middleware për autentikim?

Middleware nuk ka qasje te konteksti i ekzekutimit të Nest (p.sh. metadatat e dekoratorëve), pra nuk mund të lexojë @Roles(). Guard-et po — për këtë arsye autentikimi/autorizimi shkon gjithmonë te guard-et, jo te middleware.

8. A mund të përdor GraphQL në vend të REST?

Po, NestJS ka mbështetje zyrtare si për Apollo Server ashtu edhe për Mercurius, me të njëjtin sistem modulesh/DI — i dobishëm nëse nevojiten query fleksibël nga ana e klientit.

9. Si i menaxhoj transaksionet e databazës?

Me DataSource.transaction() të TypeORM, ose me dekoratorin @Transactional() të librarisë typeorm-transactional për një sintaksë më deklarative.

10. Si e implementoj refresh token-in në mënyrë të sigurt?

Ruaj një hash të refresh token-it anash DB (whitelist), rrotulloje me çdo përdorim (token rotation), dhe pavlefshmëro eksplicitisht te logout.

11. A mbështet NestJS mikroshërbimet?

Po, nativisht, me transport layer për TCP, Redis, RabbitMQ, Kafka, gRPC dhe NATS përmes @nestjs/microservices.

12. Si bëj deploy në prodhim?

Build me nest build (kompilon në dist/), pastaj node dist/main.js; në platforma si Railway/Render/Fly.io mjafton një Dockerfile multi-stage ose buildpack-u nativ Node.

13. Si menaxhoj variabla mjedisi të ndryshme për dev/staging/prod?

ConfigModule.forRoot({ envFilePath: ['.env.${NODE_ENV}', '.env'] }), me vlerat reale të injektuara nga platforma e hosting-ut në prodhim, jo nga skedarë .env të commit-uar.

14. Si implementoj upload drejt S3 në vend të diskut lokal?

Zëvendëso storage engine-in e Multer me multer-s3, ose menaxho upload-in manualisht te service me AWS SDK pasi të kesh marrë buffer-in në memorie.

15. Si e dokumentoj automatikisht API-në?

Me @nestjs/swagger: dekoratorët @ApiTags, @ApiProperty te DTO, dhe SwaggerModule.setup() te main.ts gjenerojnë një UI interaktive OpenAPI te /api/docs.

16. Si implementoj soft delete?

TypeORM mbështet nativisht @DeleteDateColumn() te entity: softDelete() vendos kolonën në vend që të fshijë rreshtin, dhe query-t përjashtojnë automatikisht rekordet e fshira.

17. Si menaxhoj versione të shumta të API-së njëkohësisht?

Me app.enableVersioning({ type: VersioningType.URI }) dhe dekoratorin @Version('2') te controller-at/metodat individuale që duhet të bashkëjetojnë me v1.

18. A nevojitet një ORM apo mund të përdor query SQL direkte?

Për shumicën e rasteve një ORM redukton gabimet dhe boilerplate-in; për query shumë komplekse apo kritike për performancën, TypeORM lejon gjithsesi query raw përmes QueryRunner duke mbajtur pjesën tjetër të app-it në ORM.

19. Si implementoj WebSocket në NestJS?

Me @nestjs/websockets dhe një @WebSocketGateway(), që integrohet me Socket.IO apo ws duke mbajtur të njëjtin sistem DI dhe guard si rrotat REST.

20. Si i menaxhoj cron job-et?

Me @nestjs/schedule dhe dekoratorin @Cron('0 0 * * *') mbi një metodë service-i — i dobishëm për pastrim të dhënash, dërgim digest email-esh, sinkronizime periodike.

21. Si e mbroj API-në nga sulme SQL Injection?

TypeORM/Prisma i parametrizojnë automatikisht query-t — rreziku shfaqet vetëm nëse ndërton query raw duke konkatenuar stringje manualisht, gjë që duhet shmangur gjithmonë.

22. Cila është mënyra e saktë për të menaxhuar secrets në prodhim?

Kurrë në kod apo skedarë të commit-uar: përdor variablat e mjedisit të platformës së hosting-ut ose një secret manager dedikuar (AWS Secrets Manager, Doppler, Railway Variables).

23. Si e shkallëzoj horizontalisht një API NestJS?

App-i duhet të jetë stateless (asnjë sesion lokal në memorie — përdor JWT ose Redis për gjendjen e përbashkët), pastaj replikohet pas një load balancer; për WebSocket nevojitet një adapter Redis për të sinkronizuar lidhjet mes instancave.

24. Si menaxhoj retrokompatibilitetin kur ndryshoj një skemë përgjigjeje?

Fut një fushë të re në vend që të riemërosh atë ekzistuese, deprekoje gradualisht me dokumentacion të qartë, dhe përdor versionimin e API-së për ndryshimet realisht breaking.

25. Ia vlen të shkruaj teste E2E përveç unit testeve për një API NestJS?

Po — @nestjs/testing me supertest lejon testimin e të gjithë pipeline-it (guard, pipe, interceptor të përfshirë) duke thirrur endpoint-et reale mbi një instancë app-i në memorie, duke kapur probleme integrimi që unit testet vetëm nuk i shohin.

Burime dhe Dokumentacion Zyrtar

Për të thelluar çdo temë të trajtuar në këtë udhëzues, dokumentacioni zyrtar mbetet gjithmonë burimi më i besueshëm dhe i përditësuar:

  • NestJS — dokumentacioni zyrtar: docs.nestjs.com, në veçanti seksionet mbi Guard, Interceptor, Pipe dhe Exception Filter.
  • TypeORM — dokumentacioni zyrtar: typeorm.io, për relacione, migration dhe query builder të avancuar.
  • Passport.js: passportjs.org, libraria e autentikimit mbi të cilën bazohet @nestjs/passport.
  • class-validator: repository zyrtar në GitHub, me listën e plotë të dekoratorëve të validimit të disponueshëm përtej atyre të përdorur në këtë udhëzues.
  • OWASP API Security Top 10: checklist-i referencë për sigurinë e API-ve REST, plotësues i seksionit Siguria të këtij udhëzuesi.
  • jwt.io: mjet për inspektimin dhe dekodimin e token-ave JWT gjatë debugging-ut të autentikimit.

Artikuj të Lidhur të Rekomanduar

Ky udhëzues mbulon të gjithë ciklin jetësor të një REST API NestJS, por çdo seksion mund të bëhet një thellim më vete. Ja temat natyrshëm të lidhura që ia vlen të eksplorohen më pas:

  • Modularizimi i një projekti të madh NestJS: feature module, shared module dhe barrel file.
  • Dependency Injection në NestJS e shpjeguar me shembuj: provider custom, factory provider, token injektimi.
  • Autentikimi JWT dhe Refresh Token në thellësi: token rotation, revokim dhe menaxhim multi-pajisje.
  • Role Based Access Control (RBAC) i avancuar: leje granulare përtej roleve të thjeshta.
  • Validimi me ValidationPipe dhe class-validator: validatorë custom dhe mesazhe gabimi të lokalizuara.
  • Logging i centralizuar në NestJS me Pino dhe korrelacioni i kërkesave përmes request ID.
  • Exception Filter custom për domene specifike (p.sh. gabime pagese, gabime domeni).
  • Upload skedarësh i avancuar me Multer: storage në S3, validim i përmbajtjes reale të skedarëve.
  • Scheduler dhe Cron Job në NestJS me @nestjs/schedule.
  • WebSocket me NestJS: njoftime realtime dhe scaling me Redis adapter.
  • Testing i controllerëve dhe serviceve NestJS: unit teste, mock i repository-ve, teste E2E me Supertest.

Përfundim

Ndërtimi i një REST API me NestJS do të thotë investim në një strukturë që shkallëzohet me kompleksitetin e projektit: module të izoluara mirë, Dependency Injection për testueshmëri, guard/interceptor/pipe për të ndarë cross-cutting concerns nga logjika e biznesit, dhe një ekosistem — TypeORM, Passport, class-validator, Swagger — që mbulon në mënyrë të pjekur çdo nevojë reale prodhimi. Shembulli API Blog i ndërtuar në këtë udhëzues (CRUD, JWT me refresh token, RBAC, upload skedarësh, logging i centralizuar, menaxhim gabimesh) është skeleti i ripërdorshëm për shumicën e API-ve NestJS që do të hasësh në praktikë.

Hapat e ardhshëm të rekomanduar: thellohu në modularizimin e projekteve të mëdha NestJS, autentikimin JWT dhe refresh token në detaje, modelin RBAC me leje granulare, dhe teknikat e testing-ut për controller dhe service — tema që plotësojnë natyrshëm këtë udhëzues.

💬 Shënime nga lexuesit

0 shënime

Shkruaj një shënim

Ndaj mendimin tënd, një sugjerim ose një kompliment

Shënimet e fundit

Ende asnjë shënim. Bëhu i pari që komenton!