NestJS — Node.js للمؤسسات

أفضل ممارسات REST والتسلسل والإصدار

16 دقيقة الدرس 31 من 48

أفضل ممارسات REST والتسلسل والإصدار

بناء واجهة برمجية NestJS جاهزة للإنتاج يعني الذهاب إلى أبعد من مجرّد إنجاز الأمور — يجب عليك تصميم الموارد بشكل صحيح، وإعادة رموز حالة HTTP الملائمة، وحماية البيانات الحساسة من التسرّب إلى الاستجابات، وتطوير واجهتك البرمجية دون كسر العملاء الحاليين. يتناول هذا الدرس أربعة محاور: تصميم موارد RESTful، ورموز الحالة، والتسلسل عبر ClassSerializerInterceptor، وإصدار الواجهة البرمجية.

تصميم موارد RESTful

يعتمد تصميم REST الجيّد على الأسماء لا الأفعال في مسارات URL، ويستعين بأساليب HTTP للتعبير عن الإجراءات:

  • GET /users — سرد جميع المستخدمين
  • GET /users/:id — جلب مستخدم واحد
  • POST /users — إنشاء مستخدم
  • PATCH /users/:id — تحديث جزئي (يُفضَّل على PUT للتغييرات الجزئية)
  • DELETE /users/:id — حذف مستخدم

يوفّر @nestjs/common في Nest مزخرفات @Get و@Post و@Patch و@Put و@Delete لربط المعالجات بهذه الأفعال بشكل نظيف.

رموز حالة HTTP

إعادة رمز الحالة الصحيح جزء من عقد الواجهة البرمجية. يضبط NestJS الافتراضي على 200 لمعظم المعالجات و201 للمعالجات المزيّنة بـ @Post. يمكن التجاوز بـ @HttpCode():

import { Controller, Post, Delete, Param, HttpCode, HttpStatus } from '@nestjs/common'; @Controller('users') export class UsersController { @Post() @HttpCode(HttpStatus.CREATED) // 201 — صريح وموثّق create(@Body() dto: CreateUserDto) { /* ... */ } @Delete(':id') @HttpCode(HttpStatus.NO_CONTENT) // 204 — حذف ناجح، لا جسم للاستجابة remove(@Param('id') id: string) { /* ... */ } }

رموز شائعة يجب معرفتها: 200 OK، و201 Created، و204 No Content، و400 Bad Request، و401 Unauthorized، و403 Forbidden، و404 Not Found، و409 Conflict، و422 Unprocessable Entity.

التسلسل باستخدام ClassSerializerInterceptor

عند إعادة كائن عادي، تُرسَل كل حقوله إلى العميل — بما فيها كلمات المرور والأعلام الداخلية وغيرها من البيانات الحساسة. يوفّر NestJS الـ ClassSerializerInterceptor (من @nestjs/common) لمعالجة هذا. يستخدم مزخرفات class-transformer للتحكّم بما يُكشَف تحديدًا.

تثبيت التبعية: npm install class-transformer class-validator

import { Exclude, Expose } from 'class-transformer'; export class UserEntity { id: number; email: string; @Exclude() // لا يُسلسَل في الاستجابة أبدًا passwordHash: string; @Exclude() internalFlag: boolean; constructor(partial: Partial<UserEntity>) { Object.assign(this, partial); } }

يمكن تفعيل المعترض بشكل عام في main.ts (أو على مستوى المتحكّم / المعالج باستخدام @UseInterceptors):

import { NestFactory, Reflector } from '@nestjs/core'; import { ClassSerializerInterceptor } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector))); await app.listen(3000); } bootstrap();

في المتحكّم، أعِد new UserEntity(rawUser) (لا كائنًا عاديًا) حتى يتمكّن المعترض من تطبيق المزخرفات:

@Get(':id') async findOne(@Param('id') id: string): Promise<UserEntity> { const raw = await this.usersService.findOne(+id); return new UserEntity(raw); // الحقول المزيّنة بـ @Exclude ستُحذف }
استخدم @Expose() في وضع القائمة البيضاء. إن أضفت excludeExtraneousValues: true إلى @SerializeOptions()، فلن يُدرَج إلا الحقول المزيّنة بـ @Expose(). هذا أكثر أمانًا من القائمة السوداء — تختار كل حقل صراحةً عوضًا عن تذكّر استثناء كل حقل حساس.
أعِد نسخ الكلاسات، لا الكائنات العادية. لا يحوّل ClassSerializerInterceptor إلا نسخ الكلاسات ذات بيانات class-transformer الوصفية. إن أعدت كيانًا خامًا من Prisma/TypeORM أو كائنًا عاديًا {}، فلن تحدث أي تحويل وستُكشَف جميع الحقول.

إصدار الواجهة البرمجية

يتيح الإصدار إدخال تغييرات جذرية دون كسر العملاء الحاليين. يدعم NestJS أربع استراتيجيات؛ والأكثر شيوعًا هما إصدار URI (/v1/users) وإصدار الترويسة (Accept-Version: 1). التفعيل في main.ts:

import { VersioningType } from '@nestjs/common'; // إصدار URI — /v1/... app.enableVersioning({ type: VersioningType.URI }); // أو إصدار الترويسة app.enableVersioning({ type: VersioningType.HEADER, header: 'Accept-Version' });

ثم زيّن المتحكّمات أو المعالجات الفردية:

import { Controller, Get, Version } from '@nestjs/common'; @Controller({ path: 'users', version: '1' }) // جميع المسارات تحت /v1/users export class UsersV1Controller { @Get() findAll() { return 'v1 list'; } } @Controller({ path: 'users', version: '2' }) export class UsersV2Controller { @Get() findAll() { return 'v2 list with pagination'; } @Get(':id') @Version('3') // تجاوز إلى v3 لمعالج واحد findOneV3() { return 'v3 single user'; } }
اختر استراتيجية الإصدار مبكرًا. إصدار URI هو الأكثر وضوحًا وملاءمةً للتخزين المؤقت. إصدار الترويسة يُبقي URLs نظيفةً لكنه يتطلّب من العملاء ضبط ترويسة. كلاهما مدعوم في NestJS باستدعاء واحد لـ app.enableVersioning() — ليست هناك حاجة لبنائه يدويًا.

الخلاصة

تقوم واجهات REST الإنتاجية في NestJS على أربعة محاور: روابط موجّهة بالموارد مع أفعال HTTP الصحيحة، ورموز حالة دقيقة (باستخدام @HttpCode)، والتسلسل (ClassSerializerInterceptor مع @Exclude/@Expose لمنع تسرّب الحقول الحساسة)، والإصدار (URI أو ترويسة، مفعَّل باستدعاء واحد). معًا تجعل واجهتك البرمجية قابلة للتنبّؤ وآمنة وقابلة للتطوّر.