Target Architecture — 2026 rebuild

A senior-architect blueprint for rebuilding A-List on AWS as a small team optimizing for development speed and customer experience. The spine is one TypeScript monorepo, one modular-monolith backend, and one set of shared types flowing into web, portals and mobile. Designed against the current state revealed by the 13-repo audit, not on a blank page.

End-to-end TypeScript Modular monolith · not microservices AWS-first · managed over self-managed One codebase → web + iOS + Android Strangler-fig migration · ship value fast Dev speed + customer experience first

The thesis in one paragraph

A-List is a small team running a UAE consumer marketplace. The correct shape is not microservices — it is a modular monolith with hard module boundaries, fronted by a few right-sized web/mobile apps, all sharing code through a single Turborepo + pnpm monorepo. Picking TypeScript end-to-end (Astro, React Router 7, Expo/React Native, NestJS) means one language, one type system, and one shared @alist/contracts package validating data from database to phone screen — the single biggest dev-velocity unlock for a team this size. Everything runs on AWS managed services so the org pays Amazon for reliability instead of hiring for it. We get there with a strangler-fig migration that keeps today's Laravel authoritative while new surfaces ship around it — value first, big-bang never.

The one spine that makes it work: a Turborepo monorepo with shared packages — @alist/contracts (Zod schemas + types), @alist/api-client, @alist/ui (shadcn/Tailwind), @alist/config. Web, portals, mobile and backend all import the same types. Change an API field once; every client fails to compile until it's fixed. That is how three platforms stay in lockstep without three teams.

Your preferences — scored

You invited me to overrule where warranted. Five endorsed outright; two refined with reasoning.

Your callVerdictWhere it lands
Astro for the websiteEndorseAstro 5, static-first with React islands. Perfect for content/SEO; kills the current Vue/CRA SPA hydration cost. Also fold creators-website into it and move lead-capture server-side.
Remix for the portalsEndorseYes — with a 2026 footnote: Remix is now React Router 7 framework mode. Consolidate the 3 portals into one role-aware app.
Postgres / Elasticsearch / RedisRefinePostgres (Aurora) as the system-of-record; Redis (ElastiCache) yes. But Elasticsearch is a search index, not a primary database — fed from Postgres via an outbox. Using it as a store of record is an anti-pattern (no transactions/consistency).
AWS-first, cloud-firstEndorseFully. ECS Fargate + Aurora + ElastiCache + CloudFront, three isolated accounts, CDK as code.
Not microservices — a quality architectureEndorseModular monolith is exactly right for this team. Microservices would add ops burden you can't staff. Module boundaries + EventBridge for the few async seams.
One way to build web + iOS + AndroidEndorseYou're already most of the way there — alist-android is Expo and builds all three. Consolidate on Expo / React Native, retire the native Swift app, share code via the monorepo.
Backend framework (you specified DB, not framework)RefineRecommend an end-to-end-TypeScript NestJS modular monolith over keeping PHP/Laravel — so the backend shares the language, types and validation with every client. Reached via strangler-fig, not a rewrite (see the pivotal-decision box).

● Current state

What the audit found

  • Laravel 11 monolith (alist-portal) is the de-facto everything — admin, vendor & creator portals, and the mobile API — plus a second Laravel API (alist-partner).
  • 5 different frontend stacks: Vue 3 SPA, CRA, Next.js, native Swift, Expo — no shared code, 5 hiring profiles.
  • One shared MySQL across partner / creator / portal and the abandoned demo & v2 forks — a single trust boundary.
  • Secrets committed across repos; one Bitbucket key unlocks all; prod clients hardcode a dev-*.alist.ae URL.
  • No CI/CD, no tests, no SCA; manual FTP/Xcode/EAS deploys from laptops; EOL runtimes (PHP 7.x, Strapi 4.14).
  • Auth by route placement & inline role strings; tokens in localStorage; no central RBAC.

● Target state 2026

Where the rebuild lands

  • One NestJS modular monolith on Fargate — domain modules (offers, redemption, accounts, campaigns, payments) with explicit boundaries; one deployable.
  • One TypeScript monorepo: Astro site, React Router 7 portals, Expo mobile, NestJS API — sharing @alist/contracts, api-client, ui.
  • Aurora PostgreSQL system-of-record with per-domain schemas & least-privilege users; OpenSearch index; ElastiCache Redis/Valkey.
  • All secrets in AWS Secrets Manager, injected at runtime; 3 isolated accounts (dev/staging/prod); config from SSM per account.
  • GitHub Actions gates on every merge — gitleaks, SAST, SCA, tests — OIDC deploys, blue/green with auto-rollback.
  • Cognito-issued JWTs, HttpOnly cookie sessions for portals, central CASL RBAC (default-deny).

⚑ The pivotal decision — Laravel or TypeScript backend?

This is the one call worth slowing down on. Two honest options for the backend:

Option A — Modernize Laravel in place

Lower risk · keeps a working system

ForFastest to "secure & stable"; the team already knows it; it actively ships; Laravel is a fine modular-monolith framework.
AgainstBackend stays a different language from every client — no shared types, no shared validation, two hiring profiles, duplicated DTOs. The dev-speed prize is left on the table.

Option B — End-to-end TypeScript (NestJS)

Recommended destination

ForOne language across web/portals/mobile/backend; @alist/contracts Zod schemas drive tRPC, REST, DB and forms from one source; tRPC gives the portals end-to-end type safety with zero codegen.
AgainstIt's a rewrite — real effort and risk if done big-bang.
The ruling: the destination is Option B (NestJS, TypeScript) — but reached by strangler-fig, never a big-bang rewrite. Keep Laravel 11 authoritative; put a thin TypeScript BFF in front of it; build every new surface (Astro, portals, mobile) against the BFF; migrate domain modules from Laravel → NestJS one at a time, highest-value first. If momentum stalls, a hardened modular Laravel is an acceptable resting point — so the risk is bounded at every step. What you do not do is fix the security findings by rewriting; those get patched in the live Laravel in week one (Phase 0).

Target architecture — request flow on AWS

Creators · Vendors · Partners · Admin · Public Route 53CloudFront + AWS WAF (edge, TLS, caching, security headers as IaC) ├────────────► Astro 5 site S3 (static) — public web + creators, SSG + React islands ├────────────► React Router 7 portals Fargate SSR — ONE role-aware app (admin/vendor/partner) └────────────► Expo app iOS · Android · Web from one RN codebase (EAS + OTA) tRPC (portals) + REST/OpenAPI (mobile) ALB ─► ECS Fargate: NestJS modular monolith modules: accounts · offers · redemption · campaigns · payments · webhooks ┌──────────────┬───────────┼───────────────┬──────────────────┐ Aurora PG 16 ElastiCache OpenSearch S3 + CloudFront SQS · EventBridge system-of-record Valkey/Redis search index media async seams · jobs (BullMQ) per-domain schemas cache/session fed by outbox └─────────────────────────────┬──────────────────────────────┘ Cognito (identity/JWT) · SES (email) · Secrets Manager (all creds) · CloudWatch/X-Ray + Sentry Platform: AWS Organizations → 3 isolated accounts (dev · staging · prod) · IaC: AWS CDK (TypeScript) · Deploy: GitHub Actions via OIDC, blue/green

Layer-by-layer — current → target

LayerTodayTargetWhyEffort
Public webVue 3 SPA + self-hosted Strapi v4; CRA creators site with a leaked browser tokenAstro 5 + React islands; managed CMS (Sanity); forms server-side on LambdaZero-JS-by-default speed & SEO; deletes the self-hosted Strapi attack surface; no provider token in the browserM
PortalsBlade admin + Next.js vendors + partner — 3 apps, localStorage tokens, inline role checksONE React Router 7 app, role-aware; HttpOnly cookies; central CASL RBACOne codebase for three audiences; fixes token-in-localStorage and the no-authz-framework systemic gapXL
MobileNative Swift iOS + Expo app (already iOS+Android+web); committed keys; ATS offExpo SDK 54 (RN New Arch) universal; retire Swift; SecureStore + cert pinningOne RN codebase for all platforms via the monorepo; fixes insecure storage & transport; OTA updates via EASL
BackendTwo Laravel 11 apps; SQLi, SSRF, OTP flaws; EOL PHP on someNestJS 11 modular monolith on Fargate; tRPC + REST; HMAC webhooks; BullMQEnd-to-end types with the clients; explicit module boundaries; not microservicesXL
DataOne shared MySQL across partner/creator/portal + legacy forksAurora PostgreSQL SoR (per-domain schemas, least-privilege) + OpenSearch index + RedisKills the shared-DB trust boundary; real search without ES-as-primary; PITR backupsXL
AWS platformcPanel/dev boxes; manual deploys; secrets in git; prod trusts devCDK; 3 accounts; Fargate + ALB + CloudFront/WAF; Secrets Manager; SQS/SESAccount isolation is the only hard boundary; ends the EOL-runtime treadmillL
DevSecOpsNo CI, no tests, no scanning; bus-factor-1 per repoTurborepo + GitHub Actions gates (gitleaks/SAST/SCA/tests); module ownersMakes every other choice shippable by a small team; closes the systemic root causesXL

The stack, by layer

Public web

Astro 5 · React 19 islands · Tailwind v4

WhyStatic-first, zero-JS-by-default; great Core Web Vitals & Arabic/RTL SEO via static per-locale pages.
CMSSanity (managed) replacing self-hosted Strapi; content pulled into Astro Content Collections at build.
Instead ofNext.js (overkill for a brochure) · Vue SPA (current, poor SEO)
your pick ✓ endorsed

Portals (admin/vendor/partner)

React Router 7 (Remix) · shadcn/ui · TanStack

WhySSR data loading for heavy tables; one role-aware app instead of three; HttpOnly cookie auth.
AuthzCentral CASL policy, global default-deny — replaces inline role strings.
Instead of3 separate apps · Blade admin · localStorage tokens
your pick ✓ endorsed

Mobile + shared client

Expo SDK 54 · React Native 0.81 · Expo Router

WhyOne RN codebase → iOS + Android + web; EAS Build + OTA updates; shares types/api-client/ui with the monorepo.
SecuritySecureStore for tokens, certificate pinning, ATS enforced.
RetireNative Swift a-list-ios (duplicate platform, committed keys)
your goal ✓ delivered

Backend core

NestJS 11 · Node 22 · tRPC + REST · Zod

WhyEnd-to-end TypeScript modular monolith; tRPC for portals, versioned REST/OpenAPI for mobile; HMAC-verified webhooks.
JobsBullMQ on Valkey; EventBridge for async module seams.
Instead ofMicroservices (too much ops) · big-bang rewrite (too risky)
refined from PHP

Data layer

Aurora PostgreSQL 16 · OpenSearch · Valkey

WhyPostgres = system-of-record (PostGIS for venues); OpenSearch = search index fed by outbox; Redis = cache/session/queue.
FixesPer-domain schemas + least-privilege users end the one shared MySQL.
Instead ofElasticsearch as a primary store (no transactions — anti-pattern)
refined: ES = index, not DB

AWS platform

CDK · ECS Fargate · CloudFront/WAF · Cognito

WhyLong-lived API → Fargate (not Lambda); 3 isolated accounts; secrets in Secrets Manager; SES/SQS/S3.
IaCAWS CDK in TypeScript with cdk-nag — infra reviewed like code.
Instead ofEKS/Kubernetes (too much for this team) · cPanel
your pick ✓ endorsed

AWS service map

ECS Fargate
Run the NestJS API and the React Router SSR — containers, no servers, no k8s.
Aurora PostgreSQL (Serverless v2)
System-of-record, per-domain schemas, PostGIS, read replicas, PITR backups.
ElastiCache (Valkey/Redis)
Cache, sessions, rate-limit, BullMQ job queue.
OpenSearch
Offer/venue search & discovery, fed from Postgres via an outbox — never the source of truth.
S3 + CloudFront
Static Astro site, media/uploads, global edge delivery for UAE + diaspora.
AWS WAF
Edge filtering in front of CloudFront/ALB; managed rule sets.
Cognito
Identity provider issuing short-lived JWTs + rotating refresh tokens.
Secrets Manager + SSM
All credentials (rotated) and per-account config — ends secrets-in-git.
SQS + EventBridge
Async seams between modules and reliable background processing.
SES
Transactional email (OTP, receipts, notifications).
CloudWatch + X-Ray + Sentry
Logs, traces, error monitoring — the observability the audit found missing.
Organizations (3 accounts)
dev / staging / prod hard isolation — kills the prod-trusts-dev blast radius.

Migration roadmap — strangler-fig

Sequenced to ship customer value fast while cutting the worst risk first. Laravel stays authoritative until each module is cut over — nothing breaks on day one.

Phase 0Week 1–2

Stop the bleeding no rebuild yet

  • Execute the rotation runbook (Bitbucket key first → Firebase/OAuth/Stripe/APP_KEY/DB → Slack/monday); stand up Secrets Manager and repoint the live apps.
  • Patch the live Laravel P0s in place: the 4 SQLi, SSRF, OTP-in-response, PIN IDOR, AJAX auth-bypass, ungated admin routes.
  • Add gitleaks + push protection to the existing repos immediately.
Phase 1Week 2–5

Monorepo + CI/CD spine

  • Create the Turborepo/pnpm monorepo and shared packages; GitHub Actions reusable workflow (gitleaks, Semgrep, OSV/Dependabot, tests, OIDC deploy).
  • Absorb the Expo app first (it already builds all three platforms); wire EAS CI with SSM config; retire native a-list-ios.
  • git-history purge on each repo as it's absorbed.
Phase 2Week 5–12

First customer-facing rebuilds behind a BFF

  • Stand up the TypeScript BFF fronting the hardened Laravel; bring up Aurora, Valkey, Cognito.
  • Ship the new Astro public site (replaces alist-website, retires creators-website & the vendor-portal prototype).
  • Ship the first React Router 7 portal (vendors) with HttpOnly-cookie auth, behind feature flags / per-route CloudFront so old and new run side by side.
Phase 3Month 4–7

Portal consolidation + legacy retirement

  • Migrate partner & admin portals onto React Router 7 + BFF; begin partner/creator/portal database separation.
  • Retire a-list-demo (Laravel 6, 8 P0) and alist-v2 (abandoned) — archive and revoke access; they only add attack surface.
  • Migrate Laravel domain modules into NestJS one at a time, highest-value first — keep Laravel authoritative until each is cut over.

How the rebuild closes the audit findings

Systemic finding (audit)Architectural fix
Secrets committed across repos; one Bitbucket key unlocks allSecrets Manager + SSM, runtime injection, rotation; gitleaks merge gate + history purge
One shared production database across apps + legacy forksAurora with per-domain schemas & least-privilege users; retire demo/v2; separate partner/creator/portal
Prod clients trust a hardcoded dev API URL (7 repos)3 isolated AWS accounts; API base URL read from per-account SSM, never hardcoded
No CI/CD, no tests, no SCA; manual deploysGitHub Actions gates (gitleaks/SAST/SCA/tests); OIDC blue/green deploys; reproducible Fargate images
No authorization framework — inline role strings, route-placement authCentral CASL policy with a global default-deny guard; every endpoint declares its required ability
Tokens in localStorage; weak/echoed OTP; backdoorCognito JWTs, HttpOnly cookies for portals, SecureStore on mobile; OTP rate-limit/lockout
EOL runtimes (PHP 7.x, Strapi 4.14) never patchedManaged/containerized current runtimes; Dependabot/Renovate; managed CMS removes patching burden
SQLi / SSRF / XSS / mass-assignment classesPrisma/parameterized queries, Zod validation everywhere, output encoding, allow-listed outbound fetch

Honest trade-offs