ساختن بک‌اند تایپ‌اسکریپتی با NestJS - بخش ۱: NestJS چیست و چرا؟

               NestJS
NestJS

توی این رشته نوشته‌ها قصد داریم با فریمورک NestJS آشنا شیم و بعدش با هم یک پروژه کوچیک درست کنیم.

هدف

توی این پست بیشتر با خود NestJS و ساختار اون آشنا میشیم. توی پست‌های بعدی سراغ پروژه خودمون میریم. پروژه‌ای که قرار هست درست کنیم بک‌اند برای یک اپ Todo هست. شامل نوشتن کنترلرها و سرویس‌ها، کار با دیتابیس، ورود کاربران و اعتباریابی و حالا اگر چیز دیگه‌ای هم بود میگیم?.

سرفصل‌ها

  1. فریمورک NestJS چیست و چرا؟
  2. شروع پروژه Todo
  3. دیتابیس!
  4. هر کاربر، Todoهای متفاوت

کمی درباره NestJS

فریمورک NestJS به قول سازندش اومده که یک مشکل حل نشده توی فریمورک‌های جاوااسکریپت رو حل بکنه، یعنی معماری!

فریمورک‌هایی مثل Express ،Koa و ... ابزارهای مورد نیاز برای ساختن بک‌اند رو به شما میدن و شما آزاد هستید که هر طور دلتون میخواد بخش‌های مختلف پروژه رو بچینید که این انعطاف‌پذیری خیلی زیادی به شما میده.. ولی یکی از ضعف‌های این روش این هست که همواره باید دنبال best practice ها بگردید که این برای بعضی‌ها مثل من سخته راستش.

در مقابل فریمورک NestJS شما رو ملزم می‌کنه توی یک چارچوب مشخص کدزنی کنید (و شاید انعطاف‌پذیری گزینه‌های قبلی رو نداشته باشه) ولی می‌تونید مطمئن باشید این چارچوب برگرفته از best practiceها و تجارب سالیان از بهترین فریمورک‌هاست.

(توی این پرانتز این رو هم بگم که فریمورک NestJS خیلی از ایده‌ها رو از Angular گرفته و برای کسایی که با Angular کار کردن بسیار آشنا خواهد بود.)

ساختار برنامه

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

پس ماژول یک جور دسته‌بندی منطقی برای اجزای برنامه هست تا کدهامون قاطی نشه. ولی کار اصلی توی اجزای ماژول انجام میشه.

ساختاری که توی NestJS پیشنهاد شده برای کدها در نظر بگیریم معماری سه لایه (three-layer architecture) هست.

عکس از pinte.ro
عکس از pinte.ro

توی این ساختار اولین لایه‌ای که با کاربر ارتباط داره، لایه رابط کاربری (همون کنترلرهامون) هستن. پیشنهاد شده که این لایه کمترین کد ممکن رو داشته باشه و خالی از منطق باشه.منطق اصلی برنامه توی سرویس‌ها قرار می‌گیره. سرویس‌ها پردازش‌های لازم رو انجام میدن و ارتباط با دیتابیس و دسترسی به داده‌ها هم توسط لایه Data Access فراهم می‌شه.

با این مفاهیم به صورت عملی توی پروژه آشنا میشیم. خب دیگه تئوری بسه بریم وارد کار بشیم.


شروع کار‍

فریمورک NestJS ابزار CLI خیلی قوی داره که خیلی از کارها رو راحت میکنه. برای نصب اون دستور زیر رو اجرا کنید.

npm i -g @nestjs/cli

حالا میتونیم با دستور زیر پروژه جدید ایجاد کنیم:

nest new [project-name]

بعد از اجرای این دستور (و انتخاب package manager دلخواهتون) nest براتون کلی فایل درست می‌کنه. حالا با دستور npm run start:dev، سرور شروع به کار میکنه و روی پورت ۳۰۰۰ لوکال هاست، میتونید خروجی اپ تولید شده رو ببینید!

همین. فقط میگه Hello World? ولی برای تولید این خروجی توی nest کلی اتفاق افتاده.?

کنترلر چیست

گفتیم اولین تیکه‌ای که درخواست میرسه دستش و شروع می‌کنه به تلاش برای پاسخ دادن، controllerهامون هستن.

محتویات app.controller.ts
محتویات app.controller.ts

همونطور که مشاهده می‌کنید کنترلرهامون کلاس‌های ساده هستن. این کلاس یک فیلد برای ذخیره آبجکت AppService داره و این آبجکت رو توی constructor گرفته و ذخیره کرده (چیزی که nest تولید کرده خلاصه شده همین تعریف فیلد و مقدار دهی توی تایپ‌اسکریپت هست، حالا که یاد گرفتید از همون استفاده کنید).

از decoratorها (اون تیکه‌هایی که با @ شروع شدن) استفاده زیاد و مفیدی توی nest شده. اینجا دکوراتور ‎@Controller()‎ مشخص می‌کنه که کلاس AppController حاوی متدهایی برای پردازش درخواست‌هاست و آدرس‌هایی که توی این کلاس گفتیم باید توی راهیاب (router) خود nest رجیستر بشن.

دکوراتور ‎@Get()‎ مشخص می‌کنه که این متد درخواست‌های HTTP GET رو پاسخ میده، توی پرانتز میشه مشخص کرد که برای پاسخ به چه آدرسی هست و چون پرانتز خالی هست، به صورت پیش‌فرض درخواست‌های صفحه / رو پاسخ میده.

اممم. پس این Hello World که توی آدرس /‎http://localhost:3000 دیدیم داره از اینجا میاد. پس برای فهمیدن بقیش میتونیم از همینجا پیش بریم. کاری که این متد داره می‌کنه فقط صدا زدن متد getHello روی سرویس appService هست. همونطور که قبلا هم گفتم سعی میشه محتوی متدهای controller خیلی مینیمال باشه. دلیلش هم این هست که بشه راحت‌تر منطق برنامه رو تست کرد و حتی بشه اون رو با کانال‌های مختلف دوباره استفاده کرد (REST API, GraphQL و حتی توی رندر سمت سرور).

سرویس‌ها

حالا ببینیم منطق برنامه که توی app.service.ts هست چیه...

محتوی app.service.ts
محتوی app.service.ts

همونطور که میبینید سرویس‌ها هم یک کلاس ساده هستن. اینجا از دکوراتور Injectable استفاده شده که مشخص می‌کنه این سرویس قابل Inject شدن توی بخش‌های مختلف اپلیکیشن از طریق Dependency Injection که nest برامون فراهم کرده هست! (راجع به Dependency Injection آخرِ نوشته گفتم).

توی سرویس‌ها معمولا به Data Access Layer دسترسی پیدا می‌کنیم و مواردی رو ذخیره می‌کنیم، تغییر میدیم یا حذف می‌کنیم.. اما خب توی این مثال فرستادن رشته Hello World کافی بوده.

به دلیل این پروژه تولید شده خیلی ساده نگه داشته بشه برای دسترسی به دیتابیس هیچ فایلی نیست و با این لایه توی پروژه خودمون (Todo App) آشنا میشیم.

ماژول، جایی برای وصل کردن همه چیز

محتویات app.module.ts
محتویات app.module.ts

ماژول جایی هست که اجزای مختلفمون رو معرفی می‌کنیم. اینجا nest میتونه جستوجوی خودش رو انجام بده و Dependency Injection (پایین‌تر راجع بهش می‌گم) رو برای اجزای متخلف اعمال کنه.

ماژول هم یک کلاس ساده (این دیگه خیلی سادست و هیچ بدنه‌ای نداره?) هست که دکوراتور ‎@Module()‎ روش اعمال شده. پارامتر این دکوراتور یک آبجکت هست که خصیصه (property)های اون برای این کاراست:

خصیصه imports و exports: این دو خصیصه برای ارتباط بین ماژول‌های مختلف هستن. providerهای ماژول فقط توی بقیه اجزای همون ماژول قابل دسترسی هستن. برای اینکه بقیه ماژول‌ها هم بتونن از اونا استفاده کنن باید توی آرایه exports هم افزوده بشن. آرایه imports هم ماژول‌های دیگه‌ای که توی این بهشون نیاز داریم رو مشخص می‌کنه، با افزودن اونا به این آرایه به اجزای export شده اون‌ها توی این ماژول دسترسی داریم.

خصیصه Controllers: این‌ها کنترلرهای ماژول هستن، توی router باید register بشن.

خصیصه Providers: این‌ها سرویس‌ها و دیگر اجزای Injectable ما هستن، هر جزئی از ماژول که به این‌ها نیاز داشت توسط Dependency Injection (پایین‌تر گفتم) اون‌ها رو دریافت می‌کنه.

نقطه شروع

حالا که همه اجزای برنامه رو بررسی کردیم میرسیم به نقطه شروع برنامه.

main.ts
main.ts

توی این نقطه توسط NestFactory آبجکت app رو می‌سازیم و ماژولی که توی اون شروع می‌کنه به گشتن دنبال Controllerها و Providerها و ماژول‌های دیگه که import شدن و اون‌ها رو مقداردهی می‌کنه و بعد روی پورت 3000 وب‌سرور رو اجرا می‌کنیم تا برنامه به درخواست‌ها جواب بده!

کمی عمیق‌تر بشویم

وقتی که متد create رو صدا می‌زنیم، nest شروع می‌کنه به جستوجو توی ماژولی که متد پاس دادیم (اینجا AppModule). اونموقع، از کلاس‌هایی که توی Controllerها و Providerها معرفی کردیم، instanceهایی میسازه و (این وسط کنترلرهامونم توی router رجیستر می‌کنه).

تا اینجا که نکته‌ای نداشت. ولی ما توی constructor کلاس AppController یک ورودی داشتیم، appService. با این وجود، چطوری instance این کنترلر رو ساخت؟

مثل خیلی از فریمورک‌های دیگه، nest از پترن Dependency Injection استفاده می‌کنه و براتون این پارمترها رو توی constructor فراهم می‌کنه. شما dependencyهای کلاس خودتون توی constructor مشخص می‌کنید (به لطف تایپ‌اسکریپت نوع اون‌ها رو هم مشخص می‌کنید، مثل appService: AppService). حالا nest با کمک Reflection میدونه کنترلر شما به چه کلاس‌هایی نیاز داره و میره توی بخش‌های مختلف Providerها یا Controllerها یا ... دنبال اونا میگرده، اون رو پیداشون میکنه و کلاس شما رو با در اختیار گذاشتن اون‌ها new میکنه. به این کار میگن Dependency Injection.

پایان بخش ۱

توی این بخش سعی کردیم با nest و اجزای مختلف اون آشنا بشیم. توی بخش‌های بعدی پروژه Todo App رو شروع می‌کنیم.