Multi-Tenant Ecommerce SaaS Backend
Django REST Framework · PostgreSQL · Redis · Celery · Docker · AWS
A production-ready multi-tenant ecommerce REST API: isolated store data, JWT authentication with role-based access, product catalogue, cart, orders, Stripe payments, coupons, reviews, Redis caching and asynchronous tasks — with a CI/CD pipeline that deploys to AWS.
Overview
A multi-tenant ecommerce SaaS API where multiple stores run on one platform with their data kept isolated. It covers the full commerce loop — catalogue, inventory, cart, orders and Stripe payments — plus subscriptions/browser features like coupons, reviews, search and an analytics dashboard, served through a REST API with Swagger documentation.
Problem
Building an ecommerce backend is easy; building one where many tenants can run stores on shared infrastructure — without data leaking between tenants, without a confusing permission model, and with acceptable latency under shared caching — is a harder systems problem. This project tackles tenant isolation, role-based access, cache invalidation and asynchronous work head-on, then wraps it in a CI/CD pipeline so it can actually ship.
Architecture
A GitHub Actions pipeline lints, scans, tests and builds the image, then deploys the release to an EC2 instance via SSH.
Technology Stack
| Layer | Technology |
|---|---|
| Framework | Django 5+ / Django REST Framework |
| Database | PostgreSQL 16 |
| Cache & queue | Redis 7, Celery + Redis |
| Auth | JWT (SimpleJWT) with refresh + blacklist |
| Payments | Stripe (sandbox) |
| Storage | AWS S3 / local |
| Containerisation | Docker + Docker Compose |
| Serving | Gunicorn + Nginx |
| CI/CD | GitHub Actions, deploy to AWS EC2 |
| Docs | Swagger / OpenAPI (drf-spectacular) |
| Quality gates | ruff, bandit, pytest + coverage |
Key Engineering Decisions
- Multi-tenant architecture — multiple stores on one platform with isolated data scoping per request.
- JWT auth with blacklist — access + refresh tokens, with logout revoking refresh tokens.
- RBAC with clear roles — Admin, Store Owner, Staff and Customer behaviours enforced in the API layer.
- Cache-aside with configured TTLs and invalidation — hot reads served from Redis with explicit invalidation on write.
- Cart price snapshots — prices recorded at cart time so later catalogue changes don't rewrite history.
- Async where possible — email notifications and report-style work run via Celery tasks.
API Design
The API is organised around the platform's domain resources. It exposes REST endpoints for authentication, stores, catalogue, cart, orders, payments, coupons, reviews, search and the analytics dashboard, documented with Swagger/OpenAPI via drf-spectacular. Feature highlights from the README:
- Auth — access & refresh tokens, blacklisting, logout
- Catalogue — categories, brands, products, variants, inventory
- Commerce — cart, wishlist, orders, Stripe payments, coupon discounts
- Community — reviews with moderation, verified purchases, helpful votes
- Discovery — full-text search, faceted filtering, autocomplete (cached)
- Analytics — revenue, orders, customers, products dashboard endpoint
Data Model
The Django apps map onto a relational schema in PostgreSQL 16:
- users — accounts with roles across the platform
- stores — tenant boundaries and their configuration
- categories / brands / products / variants / inventory — catalogue with stock tracking
- cart / wishlist — price snapshots and stock validation
- orders / payments — order status lifecycle and Stripe references
- coupons — percentage, fixed and free-shipping discounts
- reviews — moderation, verification and votes
- analytics — aggregated commerce metrics
Authentication & Security
- JWT authentication — access + refresh tokens with blacklist support
- Role-based access control — Admin, Store Owner, Staff, Customer
- HSTS and CSP headers configured
- CORS handled for the API
- Rate limiting on sensitive endpoints
- Input validation across API contracts
- Stripe in sandbox; image uploads validated and S3-ready
Testing
Test suites live alongside each Django app with a separate integration test module (tests/test_integration.py):
- App-level tests — users/auth, stores, products, cart, orders, reviews, analytics
- Factories for test data and a
pytest.iniconfiguration - Integration tests spanning app boundaries
- CI runs the full suite with coverage against real PostgreSQL 16 and Redis 7 containers
CI/CD
The GitHub Actions workflow (.github/workflows/ci.yml) is a four-stage pipeline:
Deployment is gated to the main branch and runs only after lint, security scan, tests and image build all pass. The image is also smoke-tested by importing Django inside the published container.
Challenges & Solutions
- Tenant isolation — scoping every query and write to the store context, so one tenant can never read another's data.
- Price integrity — cart price snapshots keep the charged total stable even when the catalogue changes later.
- Cache correctness — cache-aside with explicit invalidation on writes so stale catalogue reads are bounded.
- Async work at scale — Celery for emails and reports keeps request latency flat.
- Shipping quality gates — ruff, bandit and coverage in CI before anything reaches EC2.
What I Learned
This was my largest Django project: modularising an application into many apps/packages with clear boundaries, designing an RBAC model that survives realistic store operations, and learning when synchronous is fine and when work must move to a queue. It also taught me the value of a CI pipeline that enforces quality before deployment instead of hoping for the best.