Skip to content
All work
Marketplacelive

Ajza

Multi-platform automotive marketplace — one backend, six apps, five marketplaces under one roof.

2025 Saudi Arabia Confidential (Saudi Arabia)

// Results

6
Apps shipped (web + native)
37
Backend modules
360+
REST endpoints
5
User roles

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

DomainAutomotive parts e-commerce + on-demand car services + repair-on-wheels (Tashlih)
Surfaces1 REST + WebSocket backend · 3 Next.js web apps · 3 Flutter mobile apps
User rolesCustomer · Supplier · Representative · Admin · Super Admin
LanguagesBilingual end-to-end (Arabic RTL · English)
StatusIn production at ajza.net (multi-domain split: api., admin., partner., connect.)

Scale at a glance

MetricCount
Backend modules37
Controllers64
REST route handlers~360
TypeORM entities74
Database migrations82
Admin dashboard pages38
Partner dashboard pages28
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 (/ws namespace) 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 ValidationPipe with 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/en localization (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 WrongAppScreen deep-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.sh scripts; 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 AdminRole and Permission entities
  • 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:

FlowWhat it isKey entities
Direct ordersStandard e-commerce: cart → order → ship → deliverCart, Order, OrderItem, Payment
RFQ — Store RequestsCustomer broadcasts a request; stores send offers and negotiate via chatStoreRequest, StoreOffer, StoreRequestChat
Service BookingsAppointment-based car services (e.g. car wash) with schedule + blocked slotsStoreServiceItem, ServiceBooking, ServiceBookingItem
Tashlih — Rep OrdersOn-demand repair: representative travels to the customer with live map trackingRepOrder, RepOrderTrack, RepOffer, RepChat
External Parts3rd-party merchant catalog with commission, separate order pipelineExternalPart, 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 Service records 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 EventsGateway with room helpers (roomOrder, roomRepChat, roomStoreRequestChat, …) and a single emitNewMessage(roomKey, payload)
  • Real-time delivery verified with integration tests; production hardened with Nginx WebSocket upgrade (proxy_http_version 1.1 + Upgrade headers)
  • 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-only POST /admin/clear-data with confirm-phrase guard (preserves admins and default config)

2. Admin Dashboard — admin.ajza.net

38 routes, all production. Selected highlights:

AreaCapabilities
DashboardKPI cards, revenue charts, channel breakdown
UsersSearch/filter, wallet credit/debit, transaction exports, account-deletion queue
Companies & StoresCRUD, approval workflow, branding, store-types, settings
ProductsGlobal approval queue, brand/model/year filters
OrdersAll-orders view, status transitions, status history, supplier attribution
RepresentativesCRUD, profile, per-rep stats, orders & reviews
Rep Orders & SalesAnalytics, top reps, recent orders, embedded rep chat
Store RequestsInbox + status workflow + chat with the customer-supplier thread
Service BookingsFilters, status management, service-menu admin, blocked schedules, slot availability
ShippingPickup locations, retry/cancel shipments, OTO webhook viewer
Geography & CarsCountries/cities/areas, brand/model/year/body-type CRUD
ContentSliders, social links, contact info, terms/policy/registration links
CouponsCRUD with usage stats
NotificationsTemplates, custom broadcasts, delivery history
Registration RequestsApprove/reject supplier and rep onboarding
SettingsAdmin user management, role/block, distance thresholds, global key/value settings
StatisticsCross-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-wash with 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).

// Gallery

WhatsApp