The core backend for NUMU β a multi-tenant SaaS e-commerce platform purpose-built for the Egyptian and MENA market ("Shopify for Egypt"). Built with FastAPI and Clean Architecture, with PostgreSQL Row-Level Security for tenant isolation and a Celery worker fleet for background jobs.
Every NUMU frontend (merchant hub, admin backoffice, customer storefront, landing page) talks to this single API.
- System architecture
- Clean Architecture layering
- Request lifecycle
- Multi-tenancy
- Tech stack
- Quick start
- API docs
- Configuration
- External services
- Background jobs
- Project structure
- Testing
- Development tools
flowchart TB
subgraph Clients["NUMU clients"]
LP[numu-landing-page]
MH[numo-merchant-hub]
AD[numu-admin]
SF[numu-storefront Β· V3 BYOT]
BZ[numu-egyptian-bazaar Β· V2]
MA[numu-merchant-app Β· Expo]
PI[numu-payments-intelligence Β· Shopify app]
end
subgraph API["NUMU-api Β· FastAPI"]
HTTP[HTTP layer<br/>routes Β· middleware]
APP[Application layer<br/>use cases Β· DTOs]
CORE[Core domain<br/>entities Β· value objects]
INFRA[Infrastructure<br/>repositories Β· adapters]
end
subgraph Storage["Storage"]
DB[(PostgreSQL Β· RLS)]
R[(Redis Β· cache + Celery broker)]
OS[(Object storage Β· R2/S3/MinIO)]
end
subgraph Workers["Celery workers"]
W1[default queue]
W2[images queue]
W3[notifications]
end
subgraph External["External services"]
PM[Paymob Β· Fawry Β· InstaPay<br/>Kashier Β· Moyasar Β· Tap Β· JT Β· Stripe]
BO[Bosta Β· MyLerz Β· Shippo]
RS[Resend Β· Twilio]
WA[WhatsApp Business]
MT[Meta Β· Pixel/CAPI/OAuth]
OAI[OpenAI Β· HF Vision OCR]
ETA[ETA Egypt Β· ZATCA Saudi Β· Fawaterak]
SE[Sentry / Slack]
end
Clients -- "REST Β· httpOnly cookies" --> HTTP
HTTP --> APP --> CORE
APP --> INFRA
INFRA --> DB
INFRA --> R
INFRA --> OS
R --> Workers
Workers --> RS
Workers --> WA
Workers --> SE
INFRA --> PM
INFRA --> BO
INFRA --> MT
INFRA --> OAI
INFRA --> ETA
Strict, one-way dependencies β never reverse them.
flowchart LR
subgraph L["Dependency direction"]
direction LR
api[api/<br/>routes Β· middleware] --> application[application/<br/>use cases Β· DTOs] --> core[core/<br/>entities Β· interfaces]
api --> infrastructure[infrastructure/<br/>DB Β· adapters Β· cache]
application --> infrastructure
infrastructure -.implements.-> core
end
| Layer | Responsibility | Allowed imports |
|---|---|---|
core/ |
Pure domain β entities, value objects, exceptions, interfaces | (nothing internal) |
application/ |
Use cases + DTOs | core/ only |
infrastructure/ |
DB models, repositories, external services, cache, Celery tasks | core/ (implements interfaces) |
api/ |
HTTP routes, middleware, FastAPI deps, Pydantic schemas | All inner layers via DI |
sequenceDiagram
actor U as Client
participant MW as Middleware stack
participant R as FastAPI route
participant UC as Use case
participant Repo as Repository
participant DB as PostgreSQL (RLS)
participant C as Redis
participant E as External service
U->>MW: HTTP request (host: shop.numueg.app)
Note over MW: CORS β Logging β Tenant β CSRF<br/>ResponseTime β Sentry β RateLimit<br/>Compression β CacheHeaders β SecurityHeaders
MW->>MW: resolve tenant from subdomain<br/>SET app.current_tenant
MW->>R: dispatch
R->>UC: validate Pydantic schema Β· invoke use case
UC->>Repo: load entities
Repo->>DB: SELECT β¦ (RLS filters by tenant)
DB-->>Repo: rows
UC->>C: read/write cache (optional)
UC->>E: outbound call (optional Β· Paymob, OpenAI, β¦)
UC-->>R: result DTO
R-->>MW: serialize response
MW-->>U: HTTP response (with security + cache headers)
Shared schema + PostgreSQL Row-Level Security. Every tenant-scoped model carries a tenant_id foreign key, and the tenant middleware sets app.current_tenant per-request so RLS policies filter automatically.
flowchart LR
Sub["shop.numueg.app"] --> M[TenantMiddleware]
M -- "resolve subdomain" --> T[(public.tenants)]
M -- "SET app.current_tenant = ..." --> Conn[(per-request DB connection)]
Conn -- "every SELECT/UPDATE filtered<br/>by RLS USING (tenant_id = current_tenant)" --> Tables[(tenant-scoped tables)]
| Layer | Choice |
|---|---|
| Language | Python 3.11+ |
| Framework | FastAPI (async) |
| ORM | SQLAlchemy 2.0 (async) |
| Validation | Pydantic v2 |
| Database | PostgreSQL 15+ (Row-Level Security) |
| Cache / broker | Redis 7+ |
| Background jobs | Celery |
| Migrations | Alembic |
| Auth | JWT RS256 (access 30m + refresh 7d) Β· CSRF double-submit |
| Logging | structlog (JSON in prod, console in dev) |
| Admin panel | SQLAdmin (session-cookie auth) |
| WeasyPrint + Jinja2 (Noto Sans Arabic) | |
| Encryption | AES-256-GCM (merchant credentials at rest) |
Prerequisites: Python 3.11+, PostgreSQL 15+, Redis 7+.
# 1. Clone and install dependencies
git clone <repository-url>
cd NUMU-api
pip install -e ".[dev]"
# 2. Configure environment
cp .env.example .env
# 3. Start dependencies via Docker
docker compose -f docker/docker-compose.yml up -d db redis
# 4. Run migrations
alembic upgrade head
# 5. (optional) Seed sample data
python scripts/seed_data.py
# 6. Start the dev server
make dev # or: uvicorn src.main:app --reload --port 8000Full stack via Docker Compose:
docker compose -f docker/docker-compose.yml up --buildOnce running, the auto-generated docs live at:
| URL | UI |
|---|---|
http://localhost:8000/docs |
Swagger UI |
http://localhost:8000/redoc |
ReDoc |
http://localhost:8000/admin |
SQLAdmin web panel (session-cookie auth) |
Routes are grouped under /api/v1/:
| Prefix | Domain |
|---|---|
/auth/ |
Register Β· login Β· refresh Β· logout Β· CSRF Β· 2FA Β· email verification Β· password reset |
/stores/ |
Store CRUD (owner) |
/stores/{id}/β¦ |
Commerce: products Β· variants Β· categories Β· orders (+drafts Β· import Β· returns Β· refunds) Β· customers Β· inventory (+levels Β· transfers) Β· locations Β· gift_cards Β· bundles Β· shipments Β· shipping (+zones) |
/stores/{id}/β¦ |
Growth: coupons Β· promotions (offers v2) Β· marketing campaigns/audiences Β· email_templates Β· upsells Β· abandoned_checkouts Β· social Β· analytics (+realtime) Β· dashboard Β· ai |
/stores/{id}/β¦ |
Content: menus Β· pages Β· settings Β· onboarding Β· invoices Β· payment_proofs Β· payments Β· reconciliation Β· apps Β· order_import |
/stores/{id}/β¦ |
WhatsApp & omnichannel: whatsapp (+campaigns Β· chat Β· templates Β· opt_ins Β· scheduled_sends) Β· channels Β· threads Β· messages Β· capi |
/stores/{id}/β¦ |
Themes V3: themes Β· theme_editor_v3 Β· theme_installations Β· theme_updates Β· customizer_undo |
/themes + /marketplace/ |
Theme marketplace: upload/build Β· developer submissions Β· admin review Β· catalog Β· purchases Β· reviews Β· store install |
/storefront/store/{id}/ |
Public catalog Β· search Β· reviews Β· customer auth Β· checkout (+session) Β· gift cards Β· shipping rates Β· locations Β· payment proofs Β· pay Β· tracking |
/storefront/me/ |
Customer profile Β· addresses Β· cart Β· checkout Β· wishlist Β· returns Β· saved cards Β· data rights |
/storefront/β¦ |
store-by-subdomain/{subdomain} Β· theme_resolution Β· meta_feed (Meta Commerce XML) |
/staff/ + /roles + /permissions |
Staff invitations Β· sessions Β· access requests Β· policies Β· RBAC |
/admin/ |
Super-admin: tenants, waitlist, feedback, dashboard, platform config |
/tenants/ Β· /public/ |
Tenant registration & subdomain check Β· waitlist Β· landing config |
/billing Β· /referrals Β· /risk |
Subscriptions Β· merchant referrals Β· trust-network risk |
/shopify/ |
Shopify app surface (11 sub-routers) |
/oauth/meta |
Meta OAuth for business scopes |
/webhooks/ |
paymob Β· fawry Β· instapay Β· kashier Β· moyasar Β· jt Β· bosta Β· mylerz Β· meta Β· whatsapp Β· fawaterak Β· resend |
/ws Β· /health |
WebSocket realtime (inbox) Β· liveness probe |
| Variable | Description | Default |
|---|---|---|
DEBUG |
Enable debug mode | false |
DATABASE_URL |
PostgreSQL connection string | required |
REDIS_URL |
Redis connection string | required |
JWT_PRIVATE_KEY |
RSA private key (PEM) | required |
JWT_PUBLIC_KEY |
RSA public key (PEM) | required |
STRIPE_SECRET_KEY |
Stripe API key | optional |
RESEND_API_KEY |
Resend email API key | optional |
OPENAI_API_KEY |
OpenAI API key | optional |
R2_ACCESS_KEY_ID |
Cloudflare R2 access key | optional |
META_APP_ID |
Meta (Facebook / Instagram) App ID | optional |
META_APP_SECRET |
Meta App Secret | optional |
META_WEBHOOK_VERIFY_TOKEN |
Webhook verification token | optional |
META_GRAPH_API_VERSION |
Graph API version | v21.0 |
META_LOGIN_CONFIG_ID |
Facebook Login Config ID | optional |
INBOX_REALTIME_ENABLED |
Enable WebSocket inbox | true |
NOMINATIM_URL |
Self-hosted Nominatim base URL | optional |
LOCATIONIQ_KEY |
LocationIQ key (used if Nominatim unset) | optional |
See src/config/settings.py for the complete list.
| Category | Services |
|---|---|
| Payments | Paymob (cards + wallets) Β· Fawry Β· InstaPay Β· Kashier Β· Moyasar Β· Tap Β· JT Β· Stripe Β· Cash on Delivery |
| Shipping | Bosta Β· MyLerz (Egyptian couriers) Β· Shippo |
| Email / SMS | Resend Β· Twilio |
| Messaging | WhatsApp Business API (templates Β· campaigns Β· omnichannel inbox) |
| Marketing | Meta β Pixel, Conversions API, OAuth, custom/lookalike audiences, Messenger/Instagram inbox |
| Storage | Cloudflare R2 / MinIO / AWS S3 |
| AI | OpenAI (product descriptions, insights) Β· Hugging Face Vision (payment-proof OCR) |
| Tax / e-invoicing | ETA (Egypt) Β· ZATCA (Saudi Arabia) Β· Fawaterak |
| Maps | Self-hosted Nominatim (or) LocationIQ |
| Monitoring | Sentry Β· Slack (webhook channels, batched every 30s) |
The storefront checkout location picker calls /storefront/.../geocode/reverse, which proxies to either a self-hosted Nominatim container or LocationIQ depending on env config.
docker compose --profile geocoding up nominatimThe first run downloads ~200 MB and imports the Egypt OSM extract (~5 GB on disk, ~30 min). Subsequent restarts come up in seconds. The container runs UPDATE_MODE=continuous against Geofabrik's daily Egypt diff feed β no Celery task or manual re-import required.
flowchart LR
API[NUMU-api] -- enqueue --> R[(Redis broker)]
R --> W[Celery workers<br/>~57 task modules]
W --> Email[Resend email]
W --> WA[WhatsApp Business]
W --> Slack[Slack webhooks Β· batched 30s]
W --> Store[(R2 / S3 / MinIO)]
W --> Meta[Meta CAPI]
| Area | Tasks (selection) |
|---|---|
| Commerce | abandoned-cart recovery Β· back-in-stock Β· shipments Β· order webhooks |
| campaign sends Β· scheduled dispatcher (60s) Β· template poll (15m) Β· dead-letter purge (daily) | |
| Payments / risk | InstaPay expiry Β· COD deposit expiry Β· auto-RTO Β· risk scoring Β· fraud detection |
| Marketing | campaigns Β· promotions Β· Meta CAPI dispatch Β· social |
| Analytics | daily rollups Β· event ingest Β· retention purge Β· courier stats |
| Themes | theme build Β· upload Β· marketplace tasks |
| Lifecycle | demo cleanup Β· trial expiry Β· data retention Β· daily DB backup (03:00 UTC) Β· image processing |
Show tree
NUMU-api/
βββ alembic/ # Database migrations
βββ docker/ # Docker configuration
βββ docs/ # Documentation
βββ scripts/ # Utility scripts (seed, ops, etc.)
βββ src/
β βββ api/
β β βββ dependencies/ # FastAPI Depends() factories
β β βββ middleware/ # CORS, logging, tenant, CSRF, etc.
β β βββ responses/ # Response envelopes
β β βββ v1/
β β βββ routes/ # API endpoints grouped by domain
β β βββ schemas/ # Pydantic request/response schemas
β βββ application/
β β βββ dto/ # Data transfer objects
β β βββ services/ # Application services
β β βββ use_cases/ # Business use cases
β βββ config/ # Pydantic settings management
β βββ core/
β β βββ entities/ # Domain entities
β β βββ exceptions/ # Domain exceptions
β β βββ interfaces/ # Repository + service interfaces
β β βββ value_objects/ # Money, LocalizedString, etc.
β βββ infrastructure/
β βββ cache/ # Redis cache
β βββ database/ # SQLAlchemy setup + ORM models
β βββ external_services/ # Paymob, Bosta, OpenAI, β¦
β βββ messaging/ # Celery tasks
β βββ repositories/ # Repository implementations
βββ tests/
βββ unit/ # Unit tests
βββ integration/ # Integration tests (real DB / Redis)
βββ e2e/ # End-to-end tests
pytest # full suite
pytest --cov=src --cov-report=html # with coverage
pytest tests/unit/ # unit only
pytest tests/integration/ # integration only
pytest tests/e2e/ # end-to-end onlyruff format src/ tests/ # format
ruff check src/ tests/ # lint
mypy src/ # type check
make migrate-new # generate a new Alembic migration
make migrate # apply migrations
make seed # seed sample dataMIT.