NestJS
إطار عمل Node.js تقدمي بأولوية TypeScript لبناء تطبيقات قابلة للتوسع من جانب الخادم.
التثبيت
Section titled “التثبيت”إنشاء مشروع جديد
Section titled “إنشاء مشروع جديد”| الأمر | الوصف |
|---|---|
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 |
أوامر التطوير
Section titled “أوامر التطوير”| الأمر | الوصف |
|---|---|
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 | فحص الكود المصدري |
هيكل المشروع
Section titled “هيكل المشروع”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
توليد الكود عبر CLI
Section titled “توليد الكود عبر CLI”أوامر التوليد
Section titled “أوامر التوليد”| الأمر | الوصف |
|---|---|
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 | عرض معلومات المشروع والتبعيات |
توليد الموارد
Section titled “توليد الموارد”# مورد 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)
الوحدات
Section titled “الوحدات”مزخرفات الوحدة
Section titled “مزخرفات الوحدة”| الأمر | الوصف |
|---|---|
@Module({ imports: [UsersModule] }) | استيراد وحدة أخرى |
@Module({ controllers: [UsersController] }) | تسجيل المتحكمات |
@Module({ providers: [UsersService] }) | تسجيل الموفرين/الخدمات |
@Module({ exports: [UsersService] }) | تصدير الموفرين للوحدات الأخرى |
@Global() | جعل الوحدة متاحة عالمياً (استخدم باعتدال) |
الوحدات الديناميكية
Section titled “الوحدات الديناميكية”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 {}
المتحكمات
Section titled “المتحكمات”مزخرفات المسارات
Section titled “مزخرفات المسارات”| الأمر | الوصف |
|---|---|
@Controller('users') | تعريف متحكم مع بادئة مسار |
@Get() | معالجة طلب GET |
@Post() | معالجة طلب POST |
@Put(':id') | معالجة طلب PUT مع معامل |
@Patch(':id') | معالجة طلب PATCH |
@Delete(':id') | معالجة طلب DELETE |
@All('*') | معالجة جميع طرق HTTP |
مزخرفات المعاملات
Section titled “مزخرفات المعاملات”| الأمر | الوصف |
|---|---|
@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) | استجابة إعادة التوجيه |
مثال على المتحكم
Section titled “مثال على المتحكم”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)
}
}
الخدمات والموفرون
Section titled “الخدمات والموفرون”حقن التبعيات
Section titled “حقن التبعيات”| الأمر | الوصف |
|---|---|
@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 } | موفر بنطاق الطلب |
مثال على الخدمة
Section titled “مثال على الخدمة”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`)
}
}
}
التحقق و DTOs
Section titled “التحقق و DTOs”إعداد Class Validator
Section titled “إعداد Class Validator”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)
}
أمثلة DTO
Section titled “أمثلة DTO”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']) {}
الحراس والمصادقة
Section titled “الحراس والمصادقة”إعداد الحراس
Section titled “إعداد الحراس”| الأمر | الوصف |
|---|---|
@UseGuards(AuthGuard) | تطبيق حارس على المتحكم أو المسار |
@UseGuards(AuthGuard, RolesGuard) | ربط حراس متعددين |
app.useGlobalGuards(new AuthGuard()) | تسجيل حارس عام |
canActivate(context: ExecutionContext) | تنفيذ منطق الحارس |
@SetMetadata('roles', ['admin']) | تعيين بيانات وصفية مخصصة للحراس |
Reflector | قراءة البيانات الوصفية في الحراس |
مصادقة JWT
Section titled “مصادقة JWT”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 “البرمجيات الوسيطة والمعترضات والأنابيب”البرمجيات الوسيطة
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('*')
}
}
المعترضات
Section titled “المعترضات”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`)
}),
)
}
}
تكامل قاعدة البيانات
Section titled “تكامل قاعدة البيانات”إعداد TypeORM
Section titled “إعداد TypeORM”| الأمر | الوصف |
|---|---|
npm install @nestjs/typeorm typeorm pg | تثبيت TypeORM مع PostgreSQL |
TypeOrmModule.forRoot({ type: 'postgres', ... }) | تكوين الاتصال في AppModule |
TypeOrmModule.forFeature([User]) | تسجيل الكيان في وحدة الميزة |
@InjectRepository(User) private repo: Repository<User> | حقن المستودع |
إعداد Prisma
Section titled “إعداد Prisma”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 } })
}
}
معالجة الاستثناءات
Section titled “معالجة الاستثناءات”الاستثناءات المدمجة
Section titled “الاستثناءات المدمجة”| الأمر | الوصف |
|---|---|
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 خطأ داخلي في الخادم |
مرشح استثناءات مخصص
Section titled “مرشح استثناءات مخصص”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())
التكوين
Section titled “التكوين”تكوين البيئة
Section titled “تكوين البيئة”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)
}
}
الاختبارات
Section titled “الاختبارات”اختبارات الوحدة
Section titled “اختبارات الوحدة”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()
})
})
اختبارات E2E
Section titled “اختبارات E2E”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()
})
})
})
أفضل الممارسات
Section titled “أفضل الممارسات”-
استخدم مولد الموارد —
nest g resourceينشئ كامل هيكل CRUD (وحدة، متحكم، خدمة، DTOs، كيان، اختبار) في ثوانٍ ويحافظ على تناسق مشروعك. -
فعّل ValidationPipe العام مع whitelist — اضبط
whitelist: trueوforbidNonWhitelisted: trueلإزالة أو رفض الحقول غير المتوقعة تلقائياً من جميع الطلبات. -
استخدم الأنواع المعينة لـ DTOs —
PartialTypeوPickTypeوOmitTypeوIntersectionTypeتحافظ على جفاف DTOs من خلال اشتقاق DTOs التحديث/الاستعلام من DTO الإنشاء. -
فضّل الحقن عبر المنشئ — دع NestJS يتولى حقن التبعيات عبر المنشئات بدلاً من استخدام رموز
@Inject()يدوياً ما لم تحتاج موفرين مخصصين. -
حافظ على خفة المتحكمات — يجب أن تقوم المتحكمات فقط بتحليل المدخلات وإرجاع المخرجات. انقل كل منطق الأعمال إلى الخدمات لقابلية الاختبار وإعادة الاستخدام.
-
استخدم ConfigModule لكل الوصول إلى البيئة — لا تقرأ
process.envمباشرة في الخدمات. استخدمConfigServiceمع مخططات التحقق حتى تفشل المتغيرات المفقودة بسرعة عند بدء التشغيل. -
حدد نطاق الحراس والمعترضات بشكل مناسب — طبقها على مستوى المتحكم أو المسار باستخدام المزخرفات بدلاً من عالمياً، إلا إذا كانت تنطبق حقاً في كل مكان.
-
اكتب اختبارات وحدة و E2E — اختبر الخدمات بتبعيات وهمية باستخدام
Test.createTestingModule()، واختبر نقاط نهاية HTTP بـ E2E معsupertest. -
استخدم Prisma أو TypeORM بشكل متسق — اختر ORM واحد واستخدمه في كامل المشروع. Prisma تقدم أماناً أفضل للأنواع؛ TypeORM تقدم مرونة أكبر مع المزخرفات.
-
هيكل حسب الميزة وليس حسب النوع — اجمع الملفات حسب المجال (users/، auth/، orders/) بدلاً من حسب النوع (controllers/، services/). هذا يبقي الكود المرتبط معاً ويجعل الوحدات مكتفية ذاتياً.
-
عالج الأخطاء بالاستثناءات — ارمِ استثناءات HTTP المدمجة (
NotFoundException،ConflictException) من الخدمات. استخدم مرشحات الاستثناءات لتنسيق الأخطاء المخصصة. -
استخدم async/await بشكل متسق — NestJS يتعامل مع الوعود أصلياً. أرجع القيم غير المتزامنة من أساليب الخدمة ودع إطار العمل يسلسل الاستجابة.