A production-grade, multi-sided marketplace for automotive parts and services in Saudi Arabia. One backend, three web apps, three native mobile apps, five distinct marketplaces under one roof.
TL;DR
| Domain | Automotive parts e-commerce + on-demand car services + repair-on-wheels (Tashlih) |
| Surfaces | 1 REST + WebSocket backend · 3 Next.js web apps · 3 Flutter mobile apps |
| User roles | Customer · Supplier · Representative · Admin · Super Admin |
| Languages | Bilingual end-to-end (Arabic RTL · English) |
| Status | In production at ajza.net (multi-domain split: api., admin., partner., connect.) |
Scale at a glance
| Metric | Count |
|---|---|
| Backend modules | 37 |
| Controllers | 64 |
| REST route handlers | ~360 |
| TypeORM entities | 74 |
| Database migrations | 82 |
| Admin dashboard pages | 38 |
| Partner dashboard pages | 28 |
| Mobile feature modules (customer) | 25 |
Tech Stack
Backend — ajza-backend/
- NestJS (TypeScript) with modular feature architecture
- PostgreSQL + TypeORM (migrations-only, no synchronize)
- JWT access + refresh tokens, global guard with
@Public()/@Roles()/@CurrentUser()decorators - Socket.IO real-time gateway (
/wsnamespace) for chat + order/booking events - Cloudflare R2 (S3-compatible) object storage
- Firebase Cloud Messaging push notifications
- N-Genius payment gateway integration
- OurSMS for OTP and transactional SMS
- Swagger / OpenAPI auto-generated docs at
/docs - Global
ResponseInterceptor→ uniform{ success, data, meta }envelope - Global
ValidationPipewith class-validator and per-field 422 error mapping
Web Frontends — ajza-frontend/ (Turborepo + pnpm)
- Next.js 15 (App Router) with React Server Components
- Tailwind CSS v4 + Radix UI primitives
- React Query + Axios client (auto-unwrap, FormData, locale headers, Bearer token interceptor)
- next-intl for
ar/enlocalization (cookie-driven, RTL support) - Shared workspace packages:
@ajza/api-client,@ajza/config,@ajza/i18n,@ajza/types,@ajza/ui,@ajza/utils
Mobile — Flutter (3 native apps)
- BLoC / Cubit + Provider state management
- Dio HTTP, GetIt dependency injection
- easy_localization for ar/en
- socket_io_client for real-time
- firebase_messaging for push
- google_maps_flutter for maps and live tracking
- Role-guard pattern: a logged-in user whose backend role doesn't match the app gets a
WrongAppScreendeep-linking to the correct app's store listing
Infrastructure
- Docker Compose production deployment (
docker-compose.prod.yml) - Nginx reverse proxy with WebSocket upgrade headers (HTTP/1.1,
Connection: upgrade) - Per-subdomain routing:
api,admin,partner,connect, plus marketing - iOS deploy automation via
deploy-ios.shscripts; TestFlight distribution
System Architecture
┌────────────────────────────────────────────┐
│ Customers (Web) │
│ ajza.net · connect.ajza.net (QR) │
└────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ NestJS API (api.ajza.net) │
│ REST · WebSocket /ws · Swagger /docs · 37 modules / 64 ctrls │
├─────────────────────────────────────────────────────────────────────┤
│ Auth · Users · Stores · Products · Orders · Carts · Payments │
│ Wallet · Coupons · Reviews · Favorites · Notifications · Chat │
│ Rep Orders (Tashlih) · Store Requests (RFQ) · Service Bookings │
│ External Parts · Shipping (OTO) · Referrals · Statistics · Admin │
└─────────────────────────────────────────────────────────────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
PostgreSQL Cloudflare R2 N-Genius Firebase FCM OurSMS
(TypeORM) (file storage) (payments) (push notif.) (OTP/SMS)
▲ ▲ ▲ ▲
│ │ │ │
┌─────────────┐ ┌─────────────┐ ┌─────────────────────────────────────┐
│ Admin Web │ │ Partner Web │ │ Native Mobile (3 Flutter apps) │
│ admin.ajza │ │ partner.ajza│ │ Customer · Store · Rider (Tashlih) │
└─────────────┘ └─────────────┘ └─────────────────────────────────────┘
Entity hierarchy (commercial side)
User (Supplier)
└── Company (legal entity: CR, VAT, approval)
├── Store 1 (branch)
│ ├── Products
│ ├── Orders
│ ├── StoreServiceItems (car-wash menu)
│ └── Employees (StoreUser with permission slugs)
├── Store 2
└── Store 3
Access control model
- 5 user roles + fine-grained admin permissions via
AdminRoleandPermissionentities - Per-store employee permissions (slugs:
products.view,orders.manage,requests.view,store.settings.manage,store.team.manage, …) StoreAccessService.ensureStoreAccess(storeId, user, permission?)centralizes owner-vs-employee resolution
Five Marketplaces, One Platform
A single backend powers five distinct transactional flows:
| Flow | What it is | Key entities |
|---|---|---|
| Direct orders | Standard e-commerce: cart → order → ship → deliver | Cart, Order, OrderItem, Payment |
| RFQ — Store Requests | Customer broadcasts a request; stores send offers and negotiate via chat | StoreRequest, StoreOffer, StoreRequestChat |
| Service Bookings | Appointment-based car services (e.g. car wash) with schedule + blocked slots | StoreServiceItem, ServiceBooking, ServiceBookingItem |
| Tashlih — Rep Orders | On-demand repair: representative travels to the customer with live map tracking | RepOrder, RepOrderTrack, RepOffer, RepChat |
| External Parts | 3rd-party merchant catalog with commission, separate order pipeline | ExternalPart, external orders, commission rates |
The payment module is polymorphic — one ledger settles any of the five flows.
1. Backend — Domain Capabilities
Identity & onboarding
- Phone-OTP login + registration (rate-limited, configurable resend window)
- JWT access (15 min) + refresh (7 days, DB-persisted)
- Bilingual phone validation, country-code aware
- Supplier registration flow: register → create
Company(CR + VAT) → admin approval → create stores → add employees - Account deletion request with 30-day grace period and admin queue
Catalog
- Stores: multi-branch under a company, geo-search by nearest, status (pending/active/suspended), commission rate, rating aggregate
- Products: variants, multi-image, draft → approved → active lifecycle, condition grades, bilingual fields, admin approval
- Car reference data: Make → Model → Year → Body type → Part (full taxonomy)
- Store Types & Services: a store type (e.g. "car wash") binds a set of
Servicerecords that stores can offer
Commerce
- Multi-store cart, server-side validation
- Order lifecycle with audit trail (status history table)
- Per-supplier order views and admin global view
- Polymorphic Payments: pays for orders / external parts / store requests / service bookings / rep orders — card (N-Genius), wallet, or cash
- Wallet: credit/debit/refund/cashback ledger with admin override and CSV export
- Coupons: percent or fixed, scoped limits (global/user/order), expiry windows, usage history
- Reviews: 1–5 stars with supplier responses and admin moderation
- Favorites / wishlist, multi-address book with geo coordinates
Shipping
- OTO logistics integration with webhook handler
- Per-supplier pickup-location management
- Coverage + quote-by-weight, delivery-fee + Ajza commission, retry/cancel flows
- Tracking events surfaced to customer mobile
Communication
- Four distinct chat domains: customer↔store, customer↔representative, store-request negotiation, support
- Unified
EventsGatewaywith room helpers (roomOrder,roomRepChat,roomStoreRequestChat, …) and a singleemitNewMessage(roomKey, payload) - Real-time delivery verified with integration tests; production hardened with Nginx WebSocket upgrade (
proxy_http_version 1.1+Upgradeheaders) - Attachments + invoice uploads in store-request chats
Notifications
- FCM push with device-token registry
- Templates per channel (push / SMS / email) and per-user opt-in settings
- Admin broadcast and history with delivery status
Admin platform
- Cross-domain statistics (orders, rep orders, bookings, store requests, external parts) with time-range filters
- Activity logs, audit trails, soft-deletes on every entity
- System settings (maintenance, coming-soon, commissions, feature toggles)
- Content management: banners, FAQs, static pages (terms/privacy), contact inbox, social links
- Privacy & terms per app (customer / supplier / representative variants) — editable in admin, store-listing aware
- Danger Zone:
SUPER_ADMIN-onlyPOST /admin/clear-datawith confirm-phrase guard (preserves admins and default config)
2. Admin Dashboard — admin.ajza.net
38 routes, all production. Selected highlights:
| Area | Capabilities |
|---|---|
| Dashboard | KPI cards, revenue charts, channel breakdown |
| Users | Search/filter, wallet credit/debit, transaction exports, account-deletion queue |
| Companies & Stores | CRUD, approval workflow, branding, store-types, settings |
| Products | Global approval queue, brand/model/year filters |
| Orders | All-orders view, status transitions, status history, supplier attribution |
| Representatives | CRUD, profile, per-rep stats, orders & reviews |
| Rep Orders & Sales | Analytics, top reps, recent orders, embedded rep chat |
| Store Requests | Inbox + status workflow + chat with the customer-supplier thread |
| Service Bookings | Filters, status management, service-menu admin, blocked schedules, slot availability |
| Shipping | Pickup locations, retry/cancel shipments, OTO webhook viewer |
| Geography & Cars | Countries/cities/areas, brand/model/year/body-type CRUD |
| Content | Sliders, social links, contact info, terms/policy/registration links |
| Coupons | CRUD with usage stats |
| Notifications | Templates, custom broadcasts, delivery history |
| Registration Requests | Approve/reject supplier and rep onboarding |
| Settings | Admin user management, role/block, distance thresholds, global key/value settings |
| Statistics | Cross-domain analytics with date ranges |
3. Partner Dashboard — partner.ajza.net
28 routes. Built for store owners and authorized employees:
- Dashboard with pending orders, revenue, active products, bookings
- Orders list + detail with status transitions
- Products / Accessories / Offers full CRUD with image uploads (R2) and car-data filtering
- Branches for multi-location operators
- Store Requests inbox with chat, offer creation, and invoice upload
- Service Bookings with reject / start / complete / no-show flow;
schedule-washwith service menu, blocked schedules, appointment slot management - Team management with per-employee permission slugs (products.view/manage, orders.view/manage, requests.view/manage, store.settings.manage, store.team.manage)
- Categories per store
- Support Chat with Ajza staff
- Statistics for sales analytics
- Settings for profile, business info, terms
4. Landing Site — ajza.net
- Marketing home and product/feature pages
- Store registration flow (multi-step form → email confirm → success)
- Representative registration flow (same multi-step pattern)
- Email token verification (dynamic route)
- Privacy, terms, support pages
- 403 forbidden page for protected redirects
5. Mobile Apps (Flutter — three native apps)
Each app is a separate codebase with a distinct bundle ID and role guard. A signed-in user whose backend role doesn't match the app sees a WrongAppScreen with a deep link to the right app on the App Store.
Customer App — ajzacustomer-mobile-app/
Clean architecture (core/ + features/), BLoC pattern, 25 feature modules
- Phone-OTP login + onboarding (location gating, address setup)
- Home + product catalog + universal search + favorites
- Multi-store cart and checkout (card / wallet / cash)
- Unified Orders screen across all 4 flows: regular / car-wash / Tashlih / external parts
- Order tracking + invoice
- Wallet (Ajzaa) — top-up, transactions, saved payment methods
- Car-wash booking — browse washers, view menu, schedule appointment, pay
- Tashlih (on-demand repair) — request, rep chat, mandob (rep) live map tracking
- Store Requests (RFQ) — create request, chat-based negotiation with stores, accept offer
- External Parts — browse offers, order, track
- Addresses with map pin selection
- Notifications center and support chat
- Profile, language switch, help, privacy/terms, account deletion
Store App — ajzaastore-mobile-app/
Bundle: com.ajza.ajzastore · Display: "Ajza Store" · SUPPLIER-only
- Phone-OTP login + supplier onboarding + version-gate
- Home dashboard with sales / bookings / revenue
- Finance screen with date-range filtering and invoice list
- Products list + create/edit (image upload, variants, car compatibility)
- Incoming orders by status (new / accepted / completed / cancelled) with invoices
- Service menu + car-wash scheduling with blocked slots and booking analytics
- Offers CRUD
- Multi-branch management
- Work Team — invite employees and assign permission slugs from a granular catalog
- Store-request chat with customers
- Support chat with Ajza staff
- Profile, language, help, terms, account deletion
Rider App (Tashlih) — ajzarep-mobile-app/
Bundle: com.ajza.ajzapartner · Display: "Ajza Rider" · REPRESENTATIVE-only
- Phone-OTP login + rep registration
- Home + Finance (earnings by date range, transaction history)
- Orders by status with details and invoices
- Real-time chat with the customer per order
- Push notifications
- Support chat
- Profile, language, help, terms, account deletion
Shared mobile infrastructure
- Same Dio + GetIt + BLoC + easy_localization stack
- Shared form-validation pattern with
CustomFormField+FormErrorHandler+AppSnackbar(per-field 422 error rendering with no blocking dialogs) - Persistent Socket.IO with eager-connect + resume-on-foreground
- FCM-driven push and topic routing
Engineering Highlights (portfolio talking points)
These are concrete pieces of engineering I'd point to in an interview.
1. One backend, five marketplaces, polymorphic payments
A single payments module reconciles any of the five flows (orders, external parts, store requests, service bookings, rep orders) through a uniform interface. Wallet, coupons, and refunds work identically across all five.
2. Multi-tenant store access with per-employee permissions
StoreAccessService.ensureStoreAccess(storeId, user, permission?) resolves owner vs. employee in one place. Employees are issued permission slugs (products.manage, orders.view, requests.manage, …), and every controller method declares the slug it needs.
3. Role-segregated apps with a self-correcting guard
Three Flutter apps share a backend but each restricts to one role. On splash and login, an Ajza Store build rejects a customer or rep user with WrongAppScreen deep-linking to the right App Store listing — eliminating cross-role confusion without leaking endpoints.
4. Real-time across four chat domains
The same EventsGateway powers customer↔store, rep↔customer, store-request, and support chats. Room helpers and a single emitNewMessage(roomKey, payload) keep the surface small. Production WebSocket-upgrade issues were diagnosed at the origin Nginx and fixed by adding proxy_http_version 1.1 + Upgrade / Connection headers; persistent mobile sockets with eager+resume connect logic; integration tests cover socket delivery.
5. Bilingual end-to-end (Arabic RTL + English)
Bilingual fields (nameAr, nameEn, …) at the model layer, Accept-Language / X-localization headers driving server-side localization, next-intl on web with full RTL flipping, easy_localization on Flutter. Every screen, error message, email, and SMS template ships in both languages.
6. Field-level validation UX (no dialogs)
Backend uses NestJS ValidationPipe with per-field 422 responses. Flutter CustomFormField reads the response and surfaces inline errors via FormErrorHandler; a single AppSnackbar covers global errors. No modal popups.
7. Privileged operations with layered safety
Destructive actions like POST /admin/clear-data require SUPER_ADMIN role + a confirm-phrase + a Danger Zone modal in the admin UI, and preserve seeded super-admins and default configuration. The pattern repeats for account deletions (30-day grace), supplier deactivation, and registration approvals.
8. iOS shipping discipline
Build-number drift between staging and production caused TestFlight rejections. Added a deploy script (deploy-ios.sh) that queries live TestFlight build numbers, bumps pubspec.yaml, and uploads to both staging and prod in one run.
9. Documentation as a first-class artifact
docs/STATUS.md is the single source of truth for what exists; ARCHITECTURE.md documents API design, entity hierarchy, and access control; DATABASE_SCHEMA.md, API_ENDPOINTS_FULL.md, REALTIME.md, DOMAINS.md cover their respective concerns. archive/ retains historical implementation logs without polluting the active docs tree.
Project Structure (top level)
ajza-backend/ NestJS API (37 modules, 74 entities, 82 migrations)
ajza-frontend/ Turborepo (pnpm)
apps/admin/ Admin dashboard (port 3011, admin.ajza.net)
apps/partner/ Supplier dashboard (port 3012, partner.ajza.net)
apps/landing/ Marketing + registration (port 3010, ajza.net)
packages/ api-client, config, i18n, types, ui, utils
ajzacustomer-mobile-app/ Flutter — Customer (BLoC, clean architecture)
ajzaastore-mobile-app/ Flutter — Supplier (com.ajza.ajzastore)
ajzarep-mobile-app/ Flutter — Representative (com.ajza.ajzapartner / Ajza Rider)
deploy/ Production Docker + Nginx configuration
docs/ STATUS · ARCHITECTURE · API_ENDPOINTS · DATABASE_SCHEMA …
scripts/ Unified dev script + iOS deploy automation
My Role
End-to-end ownership across all surfaces:
- Backend: NestJS modules, entity design, migrations, JWT auth, polymorphic payments, real-time gateway, integrations (R2, FCM, N-Genius, OurSMS, OTO).
- Web: Next.js App Router pages, shared monorepo packages, Tailwind v4 migration, bilingual UI, complex tables and chat UIs.
- Mobile: All three Flutter apps — clean architecture for the customer app, role-segregated supplier and rider builds with shared infrastructure.
- DevOps: Docker production deployment, Nginx config (incl. WebSocket upgrade fix), TestFlight automation for iOS.
- Process: Source-of-truth documentation, migration discipline, integration tests for real-time delivery, layered safety on privileged operations.
What it demonstrates
- Architectural maturity — splitting a single domain into five coherent transactional flows with one polymorphic payment system, multi-tenant access control, and role-segregated client apps.
- Production discipline — migrations-only DB, real-time hardened against origin-proxy issues, soft-delete and audit logs everywhere, layered safety on destructive operations.
- Range — comfortably moving between NestJS, Next.js App Router, Flutter, PostgreSQL schema design, Nginx, and CI/CD for mobile.
- Locale-first product thinking — bilingual Arabic/English shipped end-to-end (entity → API → web → mobile → SMS/email).