تطوير الويب

تهيئة monorepo Nx 22 مع NestJS 11

5 دقائق للقراءة

السلسلة: هذا الدرس جزء من سلسلة NestJS 11. اقرأ المقال الرئيسي.

monorepo Nx 22 اليوم أنجع هيكل لمنتج ينطلق بـ API NestJS ويخطّط لإضافة عميل web أو mobile سريعاً. بدل إدارة 3 مستودعات git، 3 خطوط CI و3 مجموعات تبعيات تنحرف مع الوقت، نُشارك workspace واحداً، libs types مشتركة، وأمر nx affected الذي يُعيد بناء ما تغيّر فقط.

المتطلبات

  • Node.js 22.x LTS (Jod)
  • pnpm 9.x — موصى به للـ monorepos (ربح 40% على install مقابل npm)
  • Git 2.40+
  • حساب GitHub
  • المستوى: متوسط
  • 60-90 دقيقة

الخطوة 1 — إنشاء workspace Nx

pnpm dlx create-nx-workspace@22.6.5 acme \
  --preset=apps \
  --packageManager=pnpm \
  --nxCloud=skip
cd acme

الخيار --preset=apps ينشئ workspace أدنى بلا framework مفروض. --nxCloud=skip يُعطّل دعوة Nx Cloud. النتيجة: مجلد acme/ يحوي nx.json، package.json جذر ومجلدَي apps/ وlibs/ فارغين. nx graph يفتح واجهة web ترسم رسماً بيانياً للمشاريع.

الخطوة 2 — إضافة plugin Nest وتوليد التطبيق api

pnpm add -D @nx/nest@22.6.5
nx g @nx/nest:application api \
  --linter=eslint \
  --unitTestRunner=jest \
  --e2eTestRunner=jest

المولّد ينشئ apps/api/ ببنية NestJS المعيارية (main.ts، app.module.ts، إلخ) ومشروع apps/api-e2e/ لاختبارات التكامل. nx serve api يطلق الخادم على المنفذ 3000. للتحقق: curl http://localhost:3000/api يعيد {"message":"Hello API"}.

الخطوة 3 — إنشاء مكتبة types مشتركة

الفائدة الرئيسية لـ monorepo مشاركة الكود بين عدة تطبيقات. مكتبة shared-types تحوي DTO وenums المستخدَمة من backend والعملاء المستقبليين تتجنّب تكرار الواجهة في مستودعين وانحرافها بمرور الوقت.

pnpm add -D @nx/js@22.6.5
nx g @nx/js:library shared-types \
  --directory=libs/shared-types \
  --bundler=tsc \
  --unitTestRunner=jest

المكتبة في libs/shared-types/src/ مع index.ts يُصدّر ما تستهلكه المشاريع الأخرى. الاستيراد جانب apps/api بـ import { UserDto } from '@acme/shared-types' بفضل حلّ المسارات في tsconfig.base.json.

الخطوة 4 — تفعيل TypeScript strict وESLint مشترك

مشروع ينطلق بلا "strict": true يراكم ديون تنميط مكلفة لاحقاً. تفعيل strict منذ أول commit يفرض كود دفاعياً يكشف bugs عند build لا في الإنتاج.

// tsconfig.base.json (مقتطف)
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true
  }
}

الخيار noUncheckedIndexedAccess غالباً الأفيد لـ backend: يفرض التحقق أن وصولاً لمصفوفة أو كائن قد يُرجع undefined. يلغي عائلة كاملة من bugs «cannot read property of undefined».

الخطوة 5 — تقسيم API إلى modules أعمال

NestJS يشجّع التقسيم حسب المجال لا الطبقة التقنية. بدل مجلد controllers/، services/، dtos/ تخلط كل الميزات، ننشئ مجلداً لكل module أعمال.

nx g @nx/nest:module billing --project=api
nx g @nx/nest:controller billing --project=api --module=billing
nx g @nx/nest:service billing --project=api --module=billing

الثلاثة أوامر تنشئ apps/api/src/billing/ مع billing.module.ts، billing.controller.ts وbilling.service.ts. الـ module BillingModule لا يكشف خارجياً إلا ما يُعلنه في exports — افتراضياً لا شيء.

الخطوة 6 — ضبط CI GitHub Actions مع nx affected

# .github/workflows/ci.yml
name: CI
on:
  pull_request:
    branches: [main]
jobs:
  affected:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: actions/setup-node@v5
        with: { node-version: 22 }
      - uses: pnpm/action-setup@v6
        with: { version: 9 }
      - run: pnpm install --frozen-lockfile
      - run: npx nx affected -t lint test build --base=origin/main

fetch-depth: 0 إلزامي: بلا تاريخ git كامل، Nx لا يستطيع حساب قاعدة المقارنة ويُعيد بناء كل شيء. --base=origin/main يدلّ على فرع المرجع.

الخطوة 7 — التحقق من الرسم البياني وتثبيت التبعيات

nx graph              # واجهة web على localhost:4211
nx report             # إصدارات plugins
nx affected:graph     # رسم بياني للمتأثّرة منذ main

إن رفع nx report plugin بإصدار مختلف عن نواة Nx، الأمر nx migrate latest يُحاذي الكل ويطبّق codemods الضرورية تلقائياً. عملية يجب فعلها كل 3 أشهر للبقاء على patches الأمنية.

الخطوة 8 — الاستفادة من cache Nx المحلي

Nx يُخزّن مخرَج مهام build، test وlint في .nx/cache/. تنفيذ ثانٍ على نفس commit، حتى بعد reboot، يستعيد المخرَج فوراً بلا إعادة تنفيذ.

nx build api          # المرة الأولى: 8 ثوان
# عدّل ملف غير ذي صلة
nx build api          # المرة الثانية: cache hit، 200 مللي ثانية
nx reset              # تفريغ cache عند الحاجة

الآلية تعتمد hash محسوب من ملفات إدخال المشروع وتبعياته. إن لم يتغيّر hash، يُستعاد المخرَج.

ممارسات تنظيم libs

بدل مكتبة واحدة shared-types تنتهي بحمل كل شيء، نظّم libs حسب scope ونوع. scope يطابق المجال (billing، users، orders)، النوع يطابق طبيعة الكود (data-access، feature، ui، util). مكتبة billing-data-access تحوي استدعاءات API والأنواع؛ billing-feature تحوي منطق الأعمال؛ billing-ui تحوي المكوّنات.

قيود الاستيراد تُعلَن في .eslintrc.json عبر القاعدة @nx/enforce-module-boundaries. مكتبة موسومة type:util لا تستطيع استهلاك إلا مكتبات type:util أخرى.

أخطاء شائعة

الخطأ السبب الحل
NX 0001 عند generate Plugin Nx غير مُحاذٍ مع core nx migrate latest
Import @acme/shared-types غير مُحلَّل Path alias غائب من tsconfig.base.json أعد توليد lib أو أضف المسار
CI تُعيد البناء على كل commit fetch-depth مفقود أضف fetch-depth: 0 إلى checkout
تعارض pnpm-lock بين الفروع إصدارات pnpm مختلفة ثبّت packageManager في package.json
nx serve api لا يُعيد التحميل Watch SWC سيء الإعداد تحقّق --watch في project.json

أسئلة شائعة

لماذا pnpm بدل npm أو yarn؟ pnpm يستخدم store عالمياً وروابط رمزية بدل نسخ الحزم. على monorepo بـ 5 مشاريع، install ينتقل من 90 ثانية (npm) إلى 30 ثانية (pnpm).

هل نُفعّل Nx Cloud؟ لمشروع ناشئ، لا. cache محلي وكشف affected يغطّيان 90% من الربح. Nx Cloud يصير ملائماً عندما يتجاوز الفريق 5 مطوّرين.

هل يمكن خلط NestJS وNext.js في نفس workspace؟ نعم، حتى استخدام شائع. أضف plugin @nx/next. الفخّ الوحيد عزل متغيّرات البيئة جيداً: NEXT_PUBLIC_* جانب front، API_* جانب backend.

كيف نُهاجر مشروع NestJS موجود نحو Nx؟ الأمر nx init يكتشف مشروعاً موجوداً ويقترح هجرة تدريجية تحفظ scripts npm وبنية المجلدات.

مقالات ذات صلة

مشاركة