Problema

Majoritatea proiectelor personale încep simplu: un API, un frontend, un folder comun. Apoi adaugi un panou de administrare. Apoi un storefront. Apoi componente UI partajate. Apoi un pachet de configurare. Apoi unul de utilitare. Înainte să îți dai seama, gestionezi șase proiecte inter-dependente cu linking manual, versiuni care nu se sincronizează și build-uri care durează la nesfârșit.

Am construit platforma de e-commerce modulară tocmai pentru a aborda această problemă de scalabilitate. Este un monorepo Turborepo cu un backend Fastify, un storefront pentru clienți în Astro (cu React islands pentru interactivitate), un panou de administrare în React + Vite, și trei pachete partajate (@modular/ui, @modular/utils, @modular/config).

De ce Turborepo

Am evaluat Nx și Lerna înainte să aleg Turborepo. Factorii decisivi:

  • Zero configurare la pornire - turbo.json definește pipeline-ul de task-uri; Turborepo inferează graful de dependențe din workspace-urile package.json.
  • Remote caching - pachetele nemodificate nu sunt rebuild-uite pe mașini diferite. Când fac push în CI, doar pachetele pe care le-am modificat efectiv sunt rebuild-uite.
  • Execuție paralelă - turbo run build construiește mai întâi pachetele partajate (respectând graful de dependențe), apoi construiește toate aplicațiile în paralel.

În practică, un rebuild complet care dura 4+ minute a scăzut la sub 60 de secunde după ce prima rulare a populat cache-ul.

Fastify în loc de Express

Backend-ul folosește Fastify în loc de Express. Diferența de performanță este reală (de 2-3 ori mai rapid în benchmark-uri), dar ceea ce m-a convins a fost validarea schemelor nativă din Fastify. Fiecare rută definește schemele pentru request și response folosind Zod, iar Fastify le validează automat - fără middleware suplimentar, fără verificări manuale.

Combinat cu TypeScript, asta înseamnă că atunci când un request ajunge la handler-ul meu, am atât type safety la compile time, cât și validare la runtime. Request-urile malformate sunt respinse la nivelul framework-ului cu răspunsuri 400 corespunzătoare.

Înregistrarea Automată a Rutelor

Acesta este pattern-ul de care sunt cel mai mândru în acest proiect. În loc să import manual fiecare fișier de rute în aplicația principală:

Fiecare modul urmează o structură strictă de foldere: src/modules/<name>/route/<name>.route.ts. Un utilitar autoRegisterRoutes.ts scanează directorul modules/ la pornire, importă dinamic fiecare fișier care corespunde pattern-ului *.route.ts și îl înregistrează în Fastify.

Avantajul: pot adăuga un modul nou (de exemplu, "reviews") creând pur și simplu structura standard de foldere și fișierul de rute. Fără importuri de actualizat, fără înregistrări de memorat. Sistemul îl descoperă și îl montează automat.

Costul: înregistrarea implicită poate îngreuna urmărirea locului unde este definită o rută. Am atenuat acest lucru printr-un log la pornire care afișează fiecare rută înregistrată împreună cu numele modulului corespunzător.

RBAC Dinamic: Permisiuni Fără Modificări de Cod

La începutul proiectului aveam guard-ul tipic hard-codat: if (user.role !== 'admin') throw Forbidden(). Funcționează pentru două roluri. Se destramă când ai admin, manager, staff și viewer - fiecare cu acces diferit la entități diferite.

Sistemul de RBAC dinamic funcționează astfel:

  1. Înregistrarea entității - când un model Prisma nou este definit, un script de migrare generează permisiunile CRUD pentru acesta: product:create, product:read, product:update, product:delete.
  2. Maparea rol-permisiune - stocată în baza de date, nu în cod. Un admin primește toate permisiunile. Un member de staff primește product:read și order:read, dar nu și product:delete.
  3. Aplicarea prin middleware - un decorator preHandler din Fastify verifică dacă rolul utilizatorului autentificat deține permisiunea necesară pentru ruta curentă.

Adăugarea unei entități noi (ex: "reviews") creează automat permisiunile aferente. Acordarea acestor permisiuni unui rol este o operațiune de bază de date - nu necesită un nou deployment. Acest sistem scalează odată cu produsul fără a necesita efort de inginerie pentru fiecare resursă nouă.

Componente UI Partajate

Pachetul @modular/ui exportă componente de design system - butoane, carduri, input-uri, modale - consumate atât de storefront, cât și de panoul de administrare. Când actualizez o variantă de buton sau corectez un spațiu, ambele aplicații preiau modificarea la următorul build.

Decizia arhitecturală cheie: pachetul UI nu importă direct din Tailwind. În schimb, exportă componente nestilistizate sau minim stilizate cu atribute data-*, iar fiecare aplicație consumatoare aplică propriul tema. Storefrontul folosește o paletă caldă, orientată spre clienți; panoul de administrare folosește un layout dens, orientat pe date. Aceleași componente, aspect diferit.

Astro pentru Storefront

Storefrontul pentru clienți este construit cu Astro, care redă HTML static implicit și hidratează React "islands" doar acolo unde este necesară interactivitatea. Paginile de listare a produselor sunt complet statice (rapide, cache-abile, SEO-friendly). Coșul de cumpărături și checkout-ul sunt React islands care se hidratează la încărcare.

Această abordare hibridă înseamnă că storefrontul obține scoruri aproape perfecte în Lighthouse, susținând în același timp interacțiuni complexe pe client acolo unde este necesar.

Prisma: Schema Partajată

Backend-ul Fastify folosește Prisma pentru tot accesul la baza de date. Schema definește Products, Categories (ierarhice), Users, Orders, Cart și tabelele RBAC (Roles, Permissions, RolePermissions).

Ce apreciez în mod special la Prisma într-un context monorepo: clientul generat este tipizat, migrațiile sunt versionizate, iar scriptul de seed poate bootstrapa un mediu de development în câteva secunde.

Lecții Învățate

  1. Convenția bate configurația la scară. Înregistrarea automată și structurile standard de foldere reduc efortul cognitiv de navigare într-o bază de cod mare. Developerii noi pot găsi lucruri după pattern, nu după grep.
  2. RBAC-ul dinamic merită investiția inițială. Elimină o întreagă categorie de PR-uri de tipul "adaugă verificare de permisiune pentru funcționalitate nouă".
  3. Cache-ul Turborepo este transformator. Prima dată când CI a rulat în 45 de secunde în loc de 4 minute, am înțeles de ce contează tooling-ul pentru monorepo.
  4. Arhitectura island din Astro este subestimată. Pentru e-commerce bazat pe conținut, a livra zero JS implicit și a hidrata doar componentele interactive este compromisul corect.

Explorați proiectul la gitlab.com/ionutn0301/modular-e-commerce

Înapoi la Articole