تخطَّ إلى المحتوى

NestJS

إطار عمل Node.js تقدمي بأولوية TypeScript لبناء تطبيقات قابلة للتوسع من جانب الخادم.

الأمرالوصف
npm i -g @nestjs/cliتثبيت CLI الخاص بـ NestJS عالمياً
nest new project-nameإنشاء مشروع NestJS جديد
nest new project-name --strictإنشاء مشروع مع TypeScript صارم
nest new project-name -p pnpmإنشاء مشروع باستخدام pnpm
nest new project-name -p yarnإنشاء مشروع باستخدام Yarn
الأمرالوصف
npm run start:devبدء خادم التطوير مع إعادة التحميل التلقائي
npm run start:debugبدء مع وضع التصحيح وإعادة التحميل التلقائي
npm run start:prodبدء خادم الإنتاج
npm run buildبناء المشروع
npm run testتشغيل اختبارات الوحدة
npm run test:watchتشغيل الاختبارات في وضع المراقبة
npm run test:covتشغيل الاختبارات مع تقرير التغطية
npm run test:e2eتشغيل اختبارات من البداية إلى النهاية
npm run lintفحص الكود المصدري
src/
├── app.controller.ts       # المتحكم الجذر
├── app.controller.spec.ts  # اختبار وحدة المتحكم
├── app.module.ts           # الوحدة الجذرية
├── app.service.ts          # الخدمة الجذرية
├── main.ts                 # نقطة دخول التطبيق
├── users/
│   ├── users.module.ts     # وحدة الميزة
│   ├── users.controller.ts # متحكم الميزة
│   ├── users.service.ts    # خدمة الميزة
│   ├── dto/
│   │   ├── create-user.dto.ts
│   │   └── update-user.dto.ts
│   ├── entities/
│   │   └── user.entity.ts
│   └── users.controller.spec.ts
├── auth/
│   ├── auth.module.ts
│   ├── auth.guard.ts
│   ├── auth.service.ts
│   └── strategies/
│       └── jwt.strategy.ts
└── common/
    ├── filters/
    │   └── http-exception.filter.ts
    ├── interceptors/
    │   └── logging.interceptor.ts
    └── pipes/
        └── validation.pipe.ts
الأمرالوصف
nest generate module usersتوليد وحدة جديدة
nest generate controller usersتوليد متحكم جديد
nest generate service usersتوليد خدمة جديدة
nest generate resource usersتوليد مورد CRUD كامل (وحدة + متحكم + خدمة + DTOs)
nest generate guard authتوليد حارس مصادقة
nest generate pipe validationتوليد أنبوب تحقق
nest generate interceptor loggingتوليد معترض
nest generate middleware loggerتوليد برمجية وسيطة
nest generate filter http-exceptionتوليد مرشح استثناءات
nest generate decorator rolesتوليد مزخرف مخصص
nest generate class user.entityتوليد فئة بسيطة
nest generate interface userتوليد واجهة
nest g res users --no-specتوليد مورد بدون ملفات اختبار
nest g mo users --flatتوليد بدون إنشاء مجلد فرعي
nest infoعرض معلومات المشروع والتبعيات
# مورد CRUD كامل (سير العمل الأكثر شيوعاً)
nest g resource users

# مطالبات CLI:
# ? What transport layer do you use? REST API
# ? Would you like to generate CRUD entry points? Yes

# ينشئ:
# src/users/users.module.ts
# src/users/users.controller.ts
# src/users/users.service.ts
# src/users/dto/create-user.dto.ts
# src/users/dto/update-user.dto.ts
# src/users/entities/user.entity.ts
# src/users/users.controller.spec.ts
# يحدّث: src/app.module.ts (يستورد UsersModule)
الأمرالوصف
@Module({ imports: [UsersModule] })استيراد وحدة أخرى
@Module({ controllers: [UsersController] })تسجيل المتحكمات
@Module({ providers: [UsersService] })تسجيل الموفرين/الخدمات
@Module({ exports: [UsersService] })تصدير الموفرين للوحدات الأخرى
@Global()جعل الوحدة متاحة عالمياً (استخدم باعتدال)
import { Module, DynamicModule } from '@nestjs/common'

@Module({})
export class DatabaseModule {
  static forRoot(options: DatabaseOptions): DynamicModule {
    return {
      module: DatabaseModule,
      global: true,
      providers: [
        { provide: 'DATABASE_OPTIONS', useValue: options },
        DatabaseService,
      ],
      exports: [DatabaseService],
    }
  }

  static forRootAsync(options: DatabaseAsyncOptions): DynamicModule {
    return {
      module: DatabaseModule,
      global: true,
      imports: options.imports || [],
      providers: [
        {
          provide: 'DATABASE_OPTIONS',
          useFactory: options.useFactory,
          inject: options.inject || [],
        },
        DatabaseService,
      ],
      exports: [DatabaseService],
    }
  }
}

// الاستخدام في AppModule
@Module({
  imports: [
    DatabaseModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        host: config.get('DB_HOST'),
        port: config.get('DB_PORT'),
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}
الأمرالوصف
@Controller('users')تعريف متحكم مع بادئة مسار
@Get()معالجة طلب GET
@Post()معالجة طلب POST
@Put(':id')معالجة طلب PUT مع معامل
@Patch(':id')معالجة طلب PATCH
@Delete(':id')معالجة طلب DELETE
@All('*')معالجة جميع طرق HTTP
الأمرالوصف
@Param('id') id: stringاستخراج معامل المسار
@Param('id', ParseIntPipe) id: numberاستخراج وتحويل إلى عدد صحيح
@Query('page') page: stringاستخراج معامل الاستعلام
@Query() query: PaginationDtoاستخراج جميع معاملات الاستعلام كـ DTO
@Body() dto: CreateUserDtoاستخراج وتحديد نوع جسم الطلب
@Body('email') email: stringاستخراج حقل محدد من الجسم
@Headers('authorization') auth: stringاستخراج ترويسة الطلب
@Ip() ip: stringاستخراج عنوان IP العميل
@Req() request: Requestالوصول إلى كائن الطلب الخام
@Res() response: Responseالوصول إلى كائن الاستجابة الخام
@HttpCode(201)تعيين رمز حالة HTTP مخصص
@Header('Cache-Control', 'none')تعيين ترويسة الاستجابة
@Redirect('/new-url', 301)استجابة إعادة التوجيه
import {
  Controller, Get, Post, Put, Delete, Param, Query, Body,
  HttpCode, HttpStatus, ParseIntPipe, UseGuards, UseInterceptors,
} from '@nestjs/common'
import { UsersService } from './users.service'
import { CreateUserDto } from './dto/create-user.dto'
import { UpdateUserDto } from './dto/update-user.dto'
import { JwtAuthGuard } from '../auth/jwt-auth.guard'
import { LoggingInterceptor } from '../common/interceptors/logging.interceptor'

@Controller('users')
@UseInterceptors(LoggingInterceptor)
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  @HttpCode(HttpStatus.CREATED)
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto)
  }

  @Get()
  findAll(
    @Query('page', new ParseIntPipe({ optional: true })) page = 1,
    @Query('limit', new ParseIntPipe({ optional: true })) limit = 10,
  ) {
    return this.usersService.findAll(page, limit)
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.findOne(id)
  }

  @Put(':id')
  @UseGuards(JwtAuthGuard)
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() updateUserDto: UpdateUserDto,
  ) {
    return this.usersService.update(id, updateUserDto)
  }

  @Delete(':id')
  @UseGuards(JwtAuthGuard)
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.remove(id)
  }
}
الأمرالوصف
@Injectable()تمييز الفئة كموفر قابل للحقن
constructor(private usersService: UsersService)حقن الخدمة عبر المنشئ
@Inject('TOKEN') private configالحقن برمز مخصص
{ provide: 'TOKEN', useValue: value }تسجيل موفر قيمة
{ provide: 'TOKEN', useFactory: () => ... }تسجيل موفر مصنع
{ provide: 'TOKEN', useClass: MyClass }تسجيل موفر فئة
{ provide: 'TOKEN', useExisting: OtherService }اسم مستعار لموفر موجود
@Optional() private service?: MyServiceحقن تبعية اختياري
{ provide: 'TOKEN', scope: Scope.REQUEST }موفر بنطاق الطلب
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from './entities/user.entity'
import { CreateUserDto } from './dto/create-user.dto'
import { UpdateUserDto } from './dto/update-user.dto'

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepo: Repository<User>,
  ) {}

  async create(dto: CreateUserDto): Promise<User> {
    const exists = await this.usersRepo.findOneBy({ email: dto.email })
    if (exists) {
      throw new ConflictException('Email already registered')
    }
    const user = this.usersRepo.create(dto)
    return this.usersRepo.save(user)
  }

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

  async findOne(id: number): Promise<User> {
    const user = await this.usersRepo.findOneBy({ id })
    if (!user) throw new NotFoundException(`User #${id} not found`)
    return user
  }

  async update(id: number, dto: UpdateUserDto): Promise<User> {
    const user = await this.findOne(id)
    Object.assign(user, dto)
    return this.usersRepo.save(user)
  }

  async remove(id: number): Promise<void> {
    const result = await this.usersRepo.delete(id)
    if (result.affected === 0) {
      throw new NotFoundException(`User #${id} not found`)
    }
  }
}
npm install class-validator class-transformer
// main.ts - تفعيل التحقق العام
import { ValidationPipe } from '@nestjs/common'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,           // إزالة الخصائص غير المعروفة
    forbidNonWhitelisted: true, // رفض الخصائص غير المعروفة
    transform: true,           // تحويل الحمولات تلقائياً إلى أنواع DTO
    transformOptions: {
      enableImplicitConversion: true, // تحويل سلاسل الاستعلام إلى أرقام/قيم منطقية
    },
  }))
  await app.listen(3000)
}
import {
  IsString, IsEmail, IsOptional, IsInt, Min, Max,
  MinLength, MaxLength, IsEnum, ValidateNested, IsArray,
} from 'class-validator'
import { Type } from 'class-transformer'
import { PartialType, OmitType, PickType, IntersectionType } from '@nestjs/mapped-types'

export class CreateUserDto {
  @IsString()
  @MinLength(2)
  @MaxLength(50)
  name: string

  @IsEmail()
  email: string

  @IsString()
  @MinLength(8)
  password: string

  @IsOptional()
  @IsEnum(Role)
  role?: Role

  @IsOptional()
  @ValidateNested()
  @Type(() => AddressDto)
  address?: AddressDto
}

// توليد تلقائي لـ DTO التحديث (جميع الحقول اختيارية)
export class UpdateUserDto extends PartialType(CreateUserDto) {}

// اختيار حقول محددة
export class LoginDto extends PickType(CreateUserDto, ['email', 'password']) {}

// حذف حقول محددة
export class PublicUserDto extends OmitType(CreateUserDto, ['password']) {}
الأمرالوصف
@UseGuards(AuthGuard)تطبيق حارس على المتحكم أو المسار
@UseGuards(AuthGuard, RolesGuard)ربط حراس متعددين
app.useGlobalGuards(new AuthGuard())تسجيل حارس عام
canActivate(context: ExecutionContext)تنفيذ منطق الحارس
@SetMetadata('roles', ['admin'])تعيين بيانات وصفية مخصصة للحراس
Reflectorقراءة البيانات الوصفية في الحراس
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install -D @types/passport-jwt
// auth/auth.module.ts
@Module({
  imports: [
    UsersModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        secret: config.get('JWT_SECRET'),
        signOptions: { expiresIn: '1h' },
      }),
      inject: [ConfigService],
    }),
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController],
  exports: [AuthService],
})
export class AuthModule {}

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

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

  validate(payload: { sub: number; email: string }) {
    return { id: payload.sub, email: payload.email }
  }
}

// auth/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

// الاستخدام
@Controller('profile')
@UseGuards(JwtAuthGuard)
export class ProfileController {
  @Get()
  getProfile(@Req() req) {
    return req.user // محقون بواسطة JwtStrategy.validate()
  }
}

التحكم في الوصول القائم على الأدوار

Section titled “التحكم في الوصول القائم على الأدوار”
// roles.decorator.ts
import { SetMetadata } from '@nestjs/common'
export const Roles = (...roles: string[]) => SetMetadata('roles', roles)

// roles.guard.ts
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 {
    const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
      context.getHandler(),
      context.getClass(),
    ])
    if (!requiredRoles) return true
    const { user } = context.switchToHttp().getRequest()
    return requiredRoles.some(role => user.roles?.includes(role))
  }
}

// الاستخدام
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
  @Get('dashboard')
  @Roles('admin')
  getDashboard() {
    return { message: 'Admin dashboard' }
  }

  @Delete('users/:id')
  @Roles('admin', 'superadmin')
  removeUser(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.remove(id)
  }
}

البرمجيات الوسيطة والمعترضات والأنابيب

Section titled “البرمجيات الوسيطة والمعترضات والأنابيب”
import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response, NextFunction } from 'express'

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const start = Date.now()
    res.on('finish', () => {
      const duration = Date.now() - start
      console.log(`${req.method} ${req.path} ${res.statusCode} ${duration}ms`)
    })
    next()
  }
}

// التسجيل في الوحدة
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*')
  }
}
import {
  Injectable, NestInterceptor, ExecutionContext, CallHandler,
} from '@nestjs/common'
import { Observable, map, tap } from 'rxjs'

// تحويل شكل الاستجابة
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, { data: T }> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<{ data: T }> {
    return next.handle().pipe(
      map(data => ({ data, timestamp: new Date().toISOString() })),
    )
  }
}

// قياس وقت التنفيذ
@Injectable()
export class TimingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const start = Date.now()
    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - start
        const response = context.switchToHttp().getResponse()
        response.setHeader('X-Response-Time', `${duration}ms`)
      }),
    )
  }
}
الأمرالوصف
npm install @nestjs/typeorm typeorm pgتثبيت TypeORM مع PostgreSQL
TypeOrmModule.forRoot({ type: 'postgres', ... })تكوين الاتصال في AppModule
TypeOrmModule.forFeature([User])تسجيل الكيان في وحدة الميزة
@InjectRepository(User) private repo: Repository<User>حقن المستودع
npm install @prisma/client
npm install -D prisma
npx prisma init
// prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect()
  }

  async onModuleDestroy() {
    await this.$disconnect()
  }
}

// prisma/prisma.module.ts
@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

// الاستخدام في خدمة
@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}

  findAll() {
    return this.prisma.user.findMany()
  }

  findOne(id: number) {
    return this.prisma.user.findUniqueOrThrow({ where: { id } })
  }
}
الأمرالوصف
throw new BadRequestException('Invalid input')400 طلب غير صالح
throw new UnauthorizedException('Login required')401 غير مصرح
throw new ForbiddenException('Access denied')403 محظور
throw new NotFoundException('User not found')404 غير موجود
throw new ConflictException('Email taken')409 تعارض
throw new UnprocessableEntityException('Validation failed')422 كيان غير قابل للمعالجة
throw new InternalServerErrorException()500 خطأ داخلي في الخادم
import {
  ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus,
} from '@nestjs/common'
import { Request, Response } from 'express'

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp()
    const response = ctx.getResponse<Response>()
    const request = ctx.getRequest<Request>()

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

    const message = exception instanceof HttpException
      ? exception.getResponse()
      : 'Internal server error'

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message: typeof message === 'string' ? message : (message as any).message,
    })
  }
}

// التسجيل عالمياً في main.ts
app.useGlobalFilters(new AllExceptionsFilter())
npm install @nestjs/config
// app.module.ts
import { ConfigModule, ConfigService } from '@nestjs/config'

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: ['.env.local', '.env'],
      validationSchema: Joi.object({
        NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
        PORT: Joi.number().default(3000),
        DATABASE_URL: Joi.string().required(),
        JWT_SECRET: Joi.string().required(),
      }),
    }),
  ],
})
export class AppModule {}

// الاستخدام في أي خدمة
@Injectable()
export class AppService {
  constructor(private config: ConfigService) {}

  getPort(): number {
    return this.config.get<number>('PORT', 3000)
  }
}
import { Test, TestingModule } from '@nestjs/testing'
import { UsersService } from './users.service'
import { getRepositoryToken } from '@nestjs/typeorm'
import { User } from './entities/user.entity'

describe('UsersService', () => {
  let service: UsersService

  const mockRepository = {
    create: jest.fn(),
    save: jest.fn(),
    findOneBy: jest.fn(),
    findAndCount: jest.fn(),
    delete: jest.fn(),
  }

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        { provide: getRepositoryToken(User), useValue: mockRepository },
      ],
    }).compile()

    service = module.get<UsersService>(UsersService)
  })

  it('should create a user', async () => {
    const dto = { name: 'Alice', email: 'alice@example.com', password: 'secret123' }
    mockRepository.findOneBy.mockResolvedValue(null)
    mockRepository.create.mockReturnValue(dto)
    mockRepository.save.mockResolvedValue({ id: 1, ...dto })

    const result = await service.create(dto)
    expect(result.id).toBe(1)
    expect(mockRepository.save).toHaveBeenCalled()
  })
})
import { Test, TestingModule } from '@nestjs/testing'
import { INestApplication, ValidationPipe } from '@nestjs/common'
import * as request from 'supertest'
import { AppModule } from '../src/app.module'

describe('UsersController (e2e)', () => {
  let app: INestApplication

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile()

    app = moduleFixture.createNestApplication()
    app.useGlobalPipes(new ValidationPipe({ whitelist: true }))
    await app.init()
  })

  afterAll(async () => {
    await app.close()
  })

  it('/users (POST) creates a user', () => {
    return request(app.getHttpServer())
      .post('/users')
      .send({ name: 'Alice', email: 'alice@example.com', password: 'secret123' })
      .expect(201)
      .expect((res) => {
        expect(res.body.name).toBe('Alice')
        expect(res.body.id).toBeDefined()
      })
  })

  it('/users (GET) returns paginated list', () => {
    return request(app.getHttpServer())
      .get('/users?page=1&limit=10')
      .expect(200)
      .expect((res) => {
        expect(res.body.items).toBeInstanceOf(Array)
        expect(res.body.total).toBeDefined()
      })
  })
})
  1. استخدم مولد المواردnest g resource ينشئ كامل هيكل CRUD (وحدة، متحكم، خدمة، DTOs، كيان، اختبار) في ثوانٍ ويحافظ على تناسق مشروعك.

  2. فعّل ValidationPipe العام مع whitelist — اضبط whitelist: true و forbidNonWhitelisted: true لإزالة أو رفض الحقول غير المتوقعة تلقائياً من جميع الطلبات.

  3. استخدم الأنواع المعينة لـ DTOsPartialType و PickType و OmitType و IntersectionType تحافظ على جفاف DTOs من خلال اشتقاق DTOs التحديث/الاستعلام من DTO الإنشاء.

  4. فضّل الحقن عبر المنشئ — دع NestJS يتولى حقن التبعيات عبر المنشئات بدلاً من استخدام رموز @Inject() يدوياً ما لم تحتاج موفرين مخصصين.

  5. حافظ على خفة المتحكمات — يجب أن تقوم المتحكمات فقط بتحليل المدخلات وإرجاع المخرجات. انقل كل منطق الأعمال إلى الخدمات لقابلية الاختبار وإعادة الاستخدام.

  6. استخدم ConfigModule لكل الوصول إلى البيئة — لا تقرأ process.env مباشرة في الخدمات. استخدم ConfigService مع مخططات التحقق حتى تفشل المتغيرات المفقودة بسرعة عند بدء التشغيل.

  7. حدد نطاق الحراس والمعترضات بشكل مناسب — طبقها على مستوى المتحكم أو المسار باستخدام المزخرفات بدلاً من عالمياً، إلا إذا كانت تنطبق حقاً في كل مكان.

  8. اكتب اختبارات وحدة و E2E — اختبر الخدمات بتبعيات وهمية باستخدام Test.createTestingModule()، واختبر نقاط نهاية HTTP بـ E2E مع supertest.

  9. استخدم Prisma أو TypeORM بشكل متسق — اختر ORM واحد واستخدمه في كامل المشروع. Prisma تقدم أماناً أفضل للأنواع؛ TypeORM تقدم مرونة أكبر مع المزخرفات.

  10. هيكل حسب الميزة وليس حسب النوع — اجمع الملفات حسب المجال (users/، auth/، orders/) بدلاً من حسب النوع (controllers/، services/). هذا يبقي الكود المرتبط معاً ويجعل الوحدات مكتفية ذاتياً.

  11. عالج الأخطاء بالاستثناءات — ارمِ استثناءات HTTP المدمجة (NotFoundException، ConflictException) من الخدمات. استخدم مرشحات الاستثناءات لتنسيق الأخطاء المخصصة.

  12. استخدم async/await بشكل متسق — NestJS يتعامل مع الوعود أصلياً. أرجع القيم غير المتزامنة من أساليب الخدمة ودع إطار العمل يسلسل الاستجابة.