Repository layout
The engine is one Nx workspace (nx.json, Nx 22) holding three applications, three e2e projects and eight libraries. One package.json at the root carries every dependency; there are no per-app package files. Node 22 (.nvmrc), NestJS 10, Prisma 7, Angular 21, Analog 2. This page is the map. Development environment says how to run it and Conventions says the rules a change follows.
The applications
Section titled “The applications”apps/api/: the NestJS REST API. Dev port 53000. Nx projectapi.apps/admin/: the Angular admin dashboard. Dev port 53200. Nx projectadmin.apps/storefront/: the Analog.js storefront, rendered on the server by Nitro. Dev port 53300. Nx projectstorefront.apps/admin-e2e/,apps/storefront-e2e/: the two Playwright suites, each with its ownplaywright.config.tsand global setup.apps/admin-services-e2e/: a Playwright project that exerciseslibs/admin-services/against the real API (npm run test:services-e2e).tools/setup/: the install stepper thatsetup.shandsetup.ps1run. It rendersdeploy/templates/into a stack directory; Topology describes what it writes.
The libraries and who imports them
Section titled “The libraries and who imports them”Every library is reached through a path alias declared in tsconfig.base.json. The alias is the import; nothing imports a library by relative path across the tree.
libs/shared/common/(@common/*, projectcommon): the backend’s shared layer. The response interceptor, the exception filter, the Translatable interceptor, the guards, the decorators, the correlation and security-header middleware, money and store-config helpers, the license verifier. Imported by the API in 169 files, and by the storefront, the admin and their type libraries for the store-config schema, the palette generator and money formatting.libs/shared/permissions/(@permissions): the permission catalogue, one frozen list of modules and actions. Imported by the API, the admin, the admin services and the admin e2e suite.prisma/permission-catalogue.tsis a hand-kept copy for the seed, andapps/api/src/permission-catalogue.parity.spec.tsfails when the two drift.libs/shared/admin-routes/(@admin-routes): every/v1/admin/*path as a constant. The API controllers and the admin services both build their paths from it, 32 files on each side.libs/admin-types/(@admin-types): the Zod schemas and types the admin validates every API response against.libs/admin-services/(@admin-services): one Angular service per API module, the HTTP layer of the admin. Imported by the admin in 311 files.libs/admin-ui/(@admin-ui): the admin design-system primitives, atoms and organisms.libs/storefront-types/(@storefront-types): the Zod schemas of the storefront API surface.libs/storefront-services/(@storefront-services): the storefront’s Angular services and its browser API client,libs/storefront-services/src/lib/shared/browser-api-client.ts.
The API-only aliases are @/* (apps/api/src), @modules/* and @config/*. The admin’s own source is @admin/*.
The API
Section titled “The API”apps/api/src/main.ts boots one Nest application and apps/api/src/app.module.ts imports the modules.
- There are thirty-four module directories under
apps/api/src/modules/: analytics, assets, audit-log, auth, campaigns, carriers, cart, categories, checkout, cms, contact, countries, currencies, geo, gift-cards, inventory, license, mail, notifications, orders, payments, products, promotions, returns, reviews, search, seo, settings, shipping, storefront-config, tax, users, webhooks and wishlist. auth/holds only the permissions guard. Authentication itself is Better Auth, configured inapps/api/src/auth/auth.tsand mounted as a raw Node handler inmain.ts, not as a Nest module.- A module is a directory with a module class, one or more controllers, a service, a
dto/directory and its specs. Modules talk to each other by injecting services; a module never reaches another module’s tables through Prisma. - The database client is
apps/api/src/prisma/prisma.service.ts, one Prisma client extended with the soft-delete filter, provided byPrismaModule. - Two settings shape every URL.
API_GLOBAL_PREFIX=apimoves every route under/apiand Swagger under/api/docs;/healthis excluded from the prefix on purpose. Production sets it (.env.production.example, written bytools/setup/src/output/compose-env.ts), and the admin and storefront dev proxies assume it too, so a checkout sets it as well (see Development environment).PORTdefaults to 53000.
How it builds:
npx nx build apirunsnest buildwith the webpack builder (apps/api/nest-cli.json,apps/api/webpack.config.js). The output is two bundles:dist/apps/api/main.js, the server, anddist/apps/api/openapi-export.js, whichnpm run docs:generateruns to write the public API reference without listening.main.jsis what the container and the backend e2e harness run.npx nx serve apirunsnest start --watch.apps/api/Dockerfilerunsprisma generateandnx build api, then ships a slim runtime with the bundle,prisma/andlibs/shared/common/src(the seed imports it at run time).
The admin
Section titled “The admin”apps/admin/ is a standalone-component Angular 21 app.
- The source is
apps/admin/src/app/:core/(stores, guards, HTTP interceptors, the session bootstrap),features/(one lazy-loaded route group per module),shared/(layouts and navigation). - Tailwind 3 with
apps/admin/tailwind.config.js; the brand tokens live inapps/admin/src/styles.css. - The app’s API base is the relative
/api(apps/admin/src/environments/environment.ts), in dev and in production alike. npx nx serve adminruns the dev server on 53200 withapps/admin/proxy.conf.json, which forwards/apitohttp://localhost:53000without rewriting the path.npx nx build adminuses@angular/build:application(esbuild) and writesdist/apps/admin/browser, with production budgets of 900 kB on the initial bundle and 80 kB onmain(apps/admin/project.json).- In production
apps/admin/Dockerfileserves that directory from nginx withapps/admin/nginx.conf: hashed assets cached for a year,index.htmlnever cached,/healthzfor the health check. The container proxies nothing;/apion the admin host is the edge’s job.
The storefront
Section titled “The storefront”apps/storefront/ is an Analog.js application on Vite (apps/storefront/vite.config.ts).
- Pages are files under
apps/storefront/src/app/pages/, with a locale prefix in every route.apps/storefront/src/lib/routes.tsis the one place a route path is spelled. apps/storefront/src/app/layout/is the shell (header, footer, the session boot),apps/storefront/src/app/sections/the feature components per area (catalog, product, cart, checkout, account, auth, search, home),apps/storefront/src/app/services/the client-side state.- The server half runs on Nitro.
apps/storefront/src/server/holds the middleware (locale-redirect.ts,security-headers.ts,seo-redirect.ts), the server routes that proxyrobots.txt, the sitemaps and thellms.txtfiles to the API, andapi/_internal/revalidate.post.ts, the route the API calls after a catalogue write. apps/storefront/src/server/api-client.tsis the request-scoped client every SSR loader uses. It forwards the customer’s cookies, setsAccept-Languagefrom the route locale andX-Request-Idon every call.npx nx serve storefrontruns the Vite dev server on 53300. Its proxy sends browser calls to/v1/...on tohttp://localhost:53000/api/v1/....npx nx build storefront --configuration=productionwritesdist/apps/storefront/clientanddist/apps/storefront/analog/server/index.mjs;noderuns the latter.apps/storefront/Dockerfiledoes exactly that and listens on 54300 inside the container.- Nitro does not read the workspace path aliases, so the ones the server bundle needs are repeated as
nitro.aliasinapps/storefront/vite.config.ts.
The path of a request
Section titled “The path of a request”On an installed store the edge nginx is the first hop (deploy/templates/nginx/):
api.confproxies everything on the API host toapi:53000.admin.confproxies/api/on the admin host to the API and/to the admin container.storefront.confrewrites/v1/...on the storefront host to/api/v1/...on the API and sends/to the storefront container.- Every hop carries
X-Request-Id, taken from the client when one was sent and minted by the edge otherwise (deploy/shared/nginx-base.conf).
Inside the API, in order (apps/api/src/main.ts, apps/api/src/app.module.ts):
- Express middleware registered in
main.ts: the cache-control hardening (apps/api/src/bootstrap/cache-control-hardening.ts), the local uploads mount, the cookie parser, CORS fromCORS_ORIGIN, the Better Auth handler for/v1/auth/*(before the JSON parser, because it reads the raw body), then the JSON and urlencoded parsers with a 100 kB limit. - Nest middleware from
app.module.ts:CorrelationIdMiddleware(setsreq.idand theX-Request-Idresponse header), thenSecurityHeadersMiddleware, on every route. - The four global guards in registration order:
AppThrottlerGuard(libs/shared/common/src/guards/throttler.guard.ts, Redis-backed),SessionAuthGuard(apps/api/src/common/session-auth.guard.ts, resolves the Better Auth cookie and loads the user’s role and permissions fresh from the database intoreq.user),PermissionsGuard(apps/api/src/modules/auth/guards/permissions.guard.ts),OwnerGuardGuard(libs/shared/common/src/guards/owner-guard.guard.ts). - The global
ValidationPipewithwhitelistandforbidNonWhitelisted, which turns the body into the controller’s DTO class or answers 400VALIDATION_ERROR. - The controller method, which calls the module service, which calls
PrismaService. - On the way out, the global interceptors run in reverse registration order:
DecimalSerializationInterceptor(Prisma decimals to numbers),ClassSerializerInterceptor,TranslatableInterceptor(Translatable objects to one string per the request locale),ApiResponseInterceptor(the{ data, meta? }envelope). - Any error thrown anywhere in that chain ends in
HttpExceptionFilter(libs/shared/common/src/filters/http-exception.filter.ts), which writes{ error: { code, message, details? } }.
After the app is built and before it listens, main.ts runs assertFtsIntegrity (apps/api/src/bootstrap/fts-integrity.ts), which refuses to boot a dev or test database whose search column is missing while its trigger is still attached.
Infrastructure ports
Section titled “Infrastructure ports”docker-compose.yml at the root runs the four development services. The ports are in the 5xxxx range so they clash with nothing else on a laptop.
postgres-dev, host port 55432, databasemerchants_engine_dev.postgres-test, host port 55433, databasemerchants_engine_test.redis-dev, host port 56379.redis-test, host port 56380.
.env.example points DATABASE_URL and REDIS_PORT at the dev pair and DATABASE_TEST_URL and REDIS_TEST_PORT at the test pair. The application ports are not in the compose file:
- API 53000, and 53001 for the API every e2e harness spawns.
- Admin 53200, and 53211 for the admin e2e dev server.
- Storefront 53300, and 53311 for the storefront e2e dev server.
The Nx targets
Section titled “The Nx targets”Each app’s project.json declares the targets npx nx <target> <project> runs:
api(apps/api/project.json):build,serve,start(the built bundle),test,e2e(the Supertest suite throughtest/jest.e2e.config.ts, which depends onbuild),lint(which also lintstest/),prisma-generate.admin(apps/admin/project.json):build,serve,serve-static(the built bundle fromdist/apps/admin/browser),test,lint.storefront(apps/storefront/project.json):build,serve,serve-production(the Nitro build on 53311),test,lint,lighthouse, and the four verifiers that run against the production build:verify-bundle-budgets,verify-ssr-payloads,verify-ssr-smoke(npm run test:storefront-ssr-smoke),verify-font-subsets.- Every library has
testand, outsidelibs/shared/,lint.npm testruns everytesttarget;npm run lintruns the API’s and the common library’s.
The rest of the tree
Section titled “The rest of the tree”prisma/:schema.prisma,migrations/, the seed (prisma/seed.ts), the roles-and-permissions-only seed for a new store (prisma/seed-roles-only.ts), the scenario seeds underprisma/seeds/, and the SQL that lives outside the schema:prisma/search_fts_setup.sqland its applierprisma/apply-fts-setup.ts.prisma.config.tsat the root holds the database URL and the seed command.test/: the backend Supertest e2e suite (test/e2e/), its global setup and the store-config fixtures undertest/fixtures/store-configs/.scripts/: repository scripts and their specs, including the docs-site guard,scripts/verify-no-client-refs.tsand the e2e identity guardscripts/e2e-identity-guard.ts.deploy/: the compose and nginx templates the installer renders, and the rendered stack directories git ignores.docs/: this documentation site underdocs/site/, including the generated API reference underdocs/site/reference/, plus the security reports and two pointer files where the hand-written API catalogues used to be.