توسعه فرانت‌اند

NestJS: فریمورک Enterprise Node.js

پنج فایل کامل Controller، Service، DTO، Module و bootstrap، وابستگی‌های validation و اجرای محلی؛ مرز امنیت و ذخیره‌سازی تولید.

1404/12/23 3 دقیقه مطالعه 1,010 بازدید

یک API چند مسیر، اعتبارسنجی و منطق مشترک دارد. اگر همه کارها داخل handlerها بمانند، تغییر یک قانون می‌تواند در چند نقطه تکرار شود. NestJS قراردادهایی برای Module، Controller و Provider فراهم می‌کند تا مسئولیت‌ها مشخص‌تر شوند. این ساختار می‌تواند به تیم کمک کند، اما نام Enterprise یا استفاده از decorator تضمین امنیت، ظرفیت یا کیفیت معماری نیست.

انتخاب framework باید با مسئله پروژه هماهنگ باشد. تیمی که به قراردادهای تزریق وابستگی و مسیر اجرای مشترک نیاز دارد ممکن است از Nest سود ببرد. سرویس کوچک با نیاز محدود می‌تواند با Express مستقیم هم روشن و قابل نگهداری باشد. Nest بر adapterهای HTTP تکیه می‌کند و مقایسه آن با Express، مقایسه دو لایه با مسئولیت کاملاً یکسان نیست. ساختار اضافه هزینه یادگیری و نگهداری هم دارد.

نسخه و راه‌اندازی مستقل

راهنمای First Steps پیش‌نیاز و ساخت پروژه را توضیح می‌دهد. نمونه این مقاله برای Nest 11 و TypeScript نوشته شده است. Node.js سازگار، مانند نسخه 24، و npm لازم‌اند. در یک مسیر تازه، فرمان npx @nestjs/cli@11 new nest-demo --package-manager npm --skip-git را اجرا و وارد پوشه ساخته‌شده شوید. این فرمان برای آماده‌سازی نمونه توسط خواننده است.

سپس npm install class-validator class-transformer را اجرا کنید. پنج فایل زیر را در src با نام‌های گفته‌شده قرار دهید؛ app.module.ts و main.ts نمونه جایگزین فایل‌های هم‌نام پروژه تازه می‌شوند. بقیه فایل‌های تولیدشده لازم نیست برای این مثال import شوند. در پروژه واقعی، جایگزینی ریشه برنامه بدون ادغام قراردادهای موجود مناسب نیست. lockfile و پیکربندی TypeScript تولیدشده باید نگهداری شوند.

DTO قرارداد runtime هم می‌خواهد

راهنمای Validation اتصال ValidationPipe به class-validator و class-transformer را شرح می‌دهد. type annotation به‌تنهایی ورودی شبکه را بررسی نمی‌کند. DTO نمونه class واقعی است و decoratorها قرارداد عنوان را بیان می‌کنند. title باید string باشد، پس از trim بین یک تا صد نویسه داشته باشد و فیلدهای ناشناخته پذیرفته نشوند. این طول انتخاب آموزشی است، نه قاعده همه مدل‌ها.

import { Transform } from 'class-transformer';
import { IsString, Length } from 'class-validator';
export class CreateNoteDto {
  @Transform(({ value }) => typeof value === 'string' ? value.trim() : value)
  @IsString()
  @Length(1, 100)
  title!: string;
}

فایل create-note.dto.ts در controller به صورت import معمول وارد می‌شود، چون برای metadata زمان اجرا لازم است. import type یا interface تنها جای class مورد نیاز این قرارداد را نمی‌گیرد. تبدیل نمونه فقط روی string انجام می‌شود و عدد را بی‌خبر به عنوان تبدیل نمی‌کند. تغییر داده و اعتبارسنجی باید با سیاست محصول سازگار باشند و نباید ورودی نامناسب را صرفاً برای سبز شدن فرم اصلاح حدسی کنند.

Provider منطق و عمر دارد

مستندات Providers تزریق وابستگی و scope را توضیح می‌دهد. NotesService در نمونه فهرست حافظه‌ای را مدیریت می‌کند و controller آن را از constructor می‌گیرد. این حافظه فقط متعلق به فرایند جاری است و پس از راه‌اندازی دوباره از بین می‌رود. نمونه حداکثر صد مورد نگه می‌دارد تا رشد نامحدود ایجاد نکند. این محدودیت جای ظرفیت و ذخیره‌سازی تولید را نمی‌گیرد.

import { ConflictException, Injectable } from '@nestjs/common';
import { randomUUID } from 'node:crypto';
type Note = { id: string; title: string };
@Injectable()
export class NotesService {
  private readonly notes: Note[] = [];
  list(): Note[] { return this.notes.map(note => ({ ...note })); }
  create(title: string): Note {
    if (this.notes.length >= 100) throw new ConflictException('The local demo allows 100 notes.');
    const note = { id: randomUUID(), title };
    this.notes.push(note);
    return { ...note };
  }
}

خروجی فهرست کپی موردهای ساده است تا مصرف‌کننده reference مستقیم فهرست داخلی را تغییر ندهد. این رفتار برای مدل ساده نمونه کافی است و deep clone عمومی محسوب نمی‌شود. شناسه از randomUUID می‌آید و title همان داده اعتبارسنجی‌شده است. حافظه مشترک service با پایگاه داده چندنسخه‌ای یا cache distributed یکی نیست. افزودن providerها مسئولیت مدل داده و تراکنش را حذف نمی‌کند.

Controller مسیر را ارائه می‌کند

راهنمای Controllers اتصال route و پارامتر درخواست را شرح می‌دهد. در نمونه، GET فهرست را برمی‌گرداند و POST یک یادداشت آموزشی می‌سازد. داده خصوصی و احراز هویت وجود ندارد. ساخت موفق با رفتار پیش‌فرض POST نتیجه 201 می‌دهد و اعتبارسنجی ناموفق پاسخ مربوط را ایجاد می‌کند. قرارداد وضعیت‌ها باید برای client مستند شود تا اصلاح ورودی و تلاش دوباره از هم جدا باشند.

import { Body, Controller, Get, Post } from '@nestjs/common';
import { CreateNoteDto } from './create-note.dto.js';
import { NotesService } from './notes.service.js';
@Controller('notes')
export class NotesController {
  constructor(private readonly notes: NotesService) {}
  @Get()
  list() { return { data: this.notes.list() }; }
  @Post()
  create(@Body() body: CreateNoteDto) { return { data: this.notes.create(body.title) }; }
}

در مسیر تولید، controller بهتر است منطق دامنه گسترده را مستقیم جمع نکند. اما جدا کردن هر خط به service تازه هم لزوماً لازم نیست. مرز باید به قابلیت و امکان استفاده مجدد مرتبط باشد. برای یک پاسخ، envelope و شکل خطا را ثابت نگه دارید. راهنمای Express API نمونه دیگری از همین نیاز دارد؛ framework متفاوت، ضرورت قرارداد واضح را عوض نمی‌کند.

Module و bootstrap کامل

مستندات Modules ثبت controller و provider و مرز export را توضیح می‌دهد. AppModule نمونه هر دو جزء مورد استفاده را ثبت می‌کند. اگر service فقط نوشته شود ولی در دامنه مناسب provider نباشد، تزریق به نتیجه نمی‌رسد. importهای فایل با اسم واقعی آن‌ها هماهنگ‌اند. این نمونه module فرعی ندارد تا جریان پایه در همان پنج فایل قابل دنبال کردن بماند.

import { Module } from '@nestjs/common';
import { NotesController } from './notes.controller.js';
import { NotesService } from './notes.service.js';
@Module({ controllers: [NotesController], providers: [NotesService] })
export class AppModule {}

main.ts برنامه را می‌سازد، پیشوند api و ValidationPipe را تنظیم می‌کند و فقط روی 127.0.0.1 گوش می‌دهد. با npm run start:dev از ریشه پروژه تازه، server محلی اجرا می‌شود. GET روی http://127.0.0.1:3000/api/notes فهرست را می‌دهد. برای POST همان نشانی، client HTTP باید Content-Type برابر application/json و object دارای title ارسال کند. نمونه به پایگاه داده یا API دیگری وابسته نیست.

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api');
  app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true }));
  await app.listen(3000, '127.0.0.1');
}
bootstrap().catch(error => { console.error('Local bootstrap failed:', error.message); process.exitCode = 1; });

whitelist و forbidNonWhitelisted در pipe باعث می‌شوند فیلد ناشناخته طبق قرارداد رد شود. transform برای DTO فعال است، ولی policy تبدیل بی‌حد نوع نیست. در صورت شکست bootstrap، پیام خطا و exit code ناموفق تعیین می‌شود. این فایل‌ها خودشان درخواست آزمایشی اجرا نمی‌کنند. در تولید، اندازه بدنه، زمان پاسخ و shutdown باید با adapter و زیرساخت پروژه هماهنگ شوند؛ اتصال محلی نمونه جای آن‌ها نیست.

Guard، Pipe و Interceptor نقش جدا دارند

راهنمای Guards تصمیم درباره اجازه اجرای مسیر را توضیح می‌دهد. شناخت هویت با مجوز عمل بر رکورد یکسان نیست. Guard باید از سیاست معتبر استفاده کند و نباید client را منبع مجوز بداند. نمونه این مقاله Guard ندارد و برای داده عمومی آموزشی محدود است. افزودن یک decorator روی مسیر به‌تنهایی دسترسی امن ایجاد نمی‌کند؛ داده و تصمیم پشت آن لازم‌اند.

راهنمای Interceptors درباره رفتار پیرامون اجرای handler توضیح دارد. تبدیل نتیجه، اندازه‌گیری یا کار مشترک می‌تواند در این دامنه باشد، ولی خطا و زمان‌بندی باید روشن بمانند. Pipe برای بررسی یا تبدیل ورودی نقش دیگری دارد. پخش کردن یک قانون میان هر سه بخش می‌تواند پیگیری را دشوار کند. مسئولیت هر قاعده را قبل از انتخاب hook بنویسید.

ساختار ماژولار به مرز دامنه نیاز دارد

Module را بر اساس قابلیت و مسئولیت انتخاب کنید، نه فقط نوع فایل. یک module برای همه serviceها و یکی برای همه controllerها ممکن است رابطه حوزه‌ها را پنهان کند. وابستگی دوری هم نشانه‌ای برای بازبینی مرزهاست؛ ابزار دور زدن چرخه لزوماً مسئله طراحی را حل نمی‌کند. API عمومی module باید محدود و قابل توضیح باشد تا تغییر داخلی بی‌دلیل همه مصرف‌کننده‌ها را تحت تأثیر نگذارد.

برای provider دارای state، عمر با نوع داده هماهنگ باشد. scope پیش‌فرض مشترک در فرایند برای بعضی سرویس‌ها مناسب است، اما اطلاعات درخواست یا حساب نباید بی‌دلیل در آن بماند. درباره pattern و closure، راهنمای الگوهای JavaScript به فهم قرارداد کمک می‌کند. DI ابزار اتصال وابستگی است و جای تعیین مسئولیت و اعتبار داده را نمی‌گیرد.

تولید و عملیات

پایگاه داده، مهاجرت، تراکنش و idempotency در نمونه وجود ندارند. برای عمل حساس، باید بدانید در قطع ارتباط یا تلاش دوباره چه اتفاقی می‌افتد. log نباید داده خصوصی یا stack قابل افشا را به پاسخ عمومی تبدیل کند. شناسه درخواست و گزارش قابل پیگیری کمک می‌کنند مشکل به مسیر و نسخه مربوط وصل شود. مدل خطا و پاسخ باید برای مصرف‌کننده پایدار بماند.

در تحویل، نسخه، فایل‌ها، مسیرها و محدودیت نمونه را روشن نگه دارید. شکل DTO با runtime و schema باید هماهنگ باشد؛ TypeScript این هماهنگی را به‌تنهایی تضمین نمی‌کند. NestJS زمانی ساختار مفیدی می‌دهد که تیم از قراردادهای آن برای مرزهای واقعی استفاده کند. بهترین بودن برای همه پروژه‌های بزرگ ادعای دقیقی نیست؛ کیفیت از تصمیم درباره داده، دسترسی و مسئولیت‌ها در همان محصول می‌آید.

واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!