A production-ready e-commerce backend built with FastAPI, PostgreSQL, Redis, and Stripe. Fully Dockerised with a multi-stage build, Nginx reverse proxy, and a GitHub Actions CI/CD pipeline.
- Product catalog β full-text search, category filtering, price range, stock status, sorting, and cursor-safe pagination
- Shopping cart β works for both authenticated users and anonymous guests (session-cookie based); coupon code support
- Order management β cart-to-order conversion with automatic stock deduction; order history and cancellation
- Stripe integration β PaymentIntent flow with server-side confirmation; webhook handler for
payment_intent.succeeded/payment_intent.payment_failed - Inventory service β Redis-backed reservation system prevents overselling under high concurrency; automatic TTL release for abandoned checkouts
- Admin dashboard β product CRUD, stock adjustment, order status workflow (pending β confirmed β shipped β delivered), and revenue metrics
- Async throughout β SQLAlchemy 2.0 async ORM, asyncpg driver, aioredis
- Observability β structured JSON request logging with X-Request-ID correlation; optional Sentry SDK integration
- Rate limiting β SlowAPI (60 req/min default, 10 req/min on auth routes)
- Tests β pytest + httpx async client; SQLite in-memory DB so no Docker required to run tests
ββββββββββββββββββββββββββββββββββββββββββββββββ
β Docker Network β
β β
Client ββββββββββββββ>β Nginx :80 FastAPI App :8000 β
Browser / Mobile β βββββββββββββ> ββββββββββββββββββββββββ> βββ> Stripe API
β Rate limit /api/v1/* β (Payments)
β Gzip Pydantic v2 validation β
β Security headers Async SQLAlchemy 2.0 β
β βββββββββββββββ¬βββββββββ β
β β β
β ββββββββββββββββββββββ€ β
β v v β
β PostgreSQL 16 :5432 Redis 7 :6379 β
β βββββββββββββββββ ββββββββββββββ β
β Users Cart sessions β
β Products / Categories Stock reservations β
β Orders / OrderItems Rate limit countersβ
β Carts / CartItems β
ββββββββββββββββββββββββββββββββββββββββββββββββ
User ββ< Order ββ< OrderItem >ββ Product >ββ Category
β
βββββ Cart ββ< CartItem >ββ Product
User has many Orders and one Cart. Order has many OrderItems (product snapshot at purchase time β denormalised for historical accuracy). Cart has many CartItems pointing to live Products. Product belongs to a Category (self-referencing tree for subcategories).
Key design decisions:
OrderItemstoresproduct_name,product_sku, andunit_priceas a snapshot β updates to the product catalogue do not alter past orders.CartItem.unit_priceis set when an item is added, allowing comparison with the live price at checkout.- Stock deductions happen inside the same DB transaction that creates the
Orderrow, so they are always atomic.
| Parameter | Type | Description |
|---|---|---|
q |
string | Full-text search (name, SKU, description) |
category_id |
int | Filter by category |
min_price |
decimal | Minimum price |
max_price |
decimal | Maximum price |
in_stock |
bool | true = only in-stock items |
is_featured |
bool | Filter featured products |
sort_by |
string | created_at | price | name | updated_at |
sort_order |
string | asc | desc |
page |
int | Page number (1-based) |
page_size |
int | Items per page (max 100) |
Response β PaginatedProductResponse:
{
"items": [ { "id": "...", "sku": "...", "name": "...", "price": "49.99", ... } ],
"total": 42,
"page": 1,
"page_size": 20,
"total_pages": 3,
"has_next": true,
"has_prev": false
}| Method | Path | Description |
|---|---|---|
GET |
/api/v1/products |
Paginated list with filters & search |
GET |
/api/v1/products/{id} |
Product detail |
GET |
/api/v1/products/slug/{slug} |
Product by URL slug |
GET |
/api/v1/categories |
List active categories |
POST |
/api/v1/categories |
Create category |
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/cart |
Get current cart |
GET |
/api/v1/cart/summary |
Lightweight count + total |
POST |
/api/v1/cart/items |
Add item (body: {product_id, quantity}) |
PATCH |
/api/v1/cart/items/{id} |
Update quantity |
DELETE |
/api/v1/cart/items/{id} |
Remove item |
DELETE |
/api/v1/cart |
Clear entire cart |
POST |
/api/v1/cart/coupon |
Apply coupon code |
DELETE |
/api/v1/cart/coupon |
Remove coupon |
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/orders |
Place order β returns Stripe client_secret |
GET |
/api/v1/orders |
Order history (paginated) |
GET |
/api/v1/orders/{id} |
Order detail |
POST |
/api/v1/orders/{id}/cancel |
Cancel pending/confirmed order |
POST |
/api/v1/webhooks/stripe |
Stripe webhook receiver |
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/admin/dashboard |
Revenue, order counts, stock alerts |
GET |
/api/v1/admin/products |
All products (admin filters) |
POST |
/api/v1/admin/products |
Create product |
PATCH |
/api/v1/admin/products/{id} |
Update product |
DELETE |
/api/v1/admin/products/{id} |
Soft-delete |
PATCH |
/api/v1/admin/products/{id}/stock |
Adjust stock (delta Β± int) |
GET |
/api/v1/admin/orders |
All orders with status filter |
GET |
/api/v1/admin/orders/{id} |
Order detail |
PATCH |
/api/v1/admin/orders/{id}/status |
Update status + tracking |
- Docker β₯ 24 and Docker Compose v2
- A Stripe account (test-mode keys are sufficient)
git clone https://github.com/shahabRDZ/ecommerce-api.git
cd ecommerce-api
cp .env.example .envEdit .env β at minimum set:
SECRET_KEY=<random 32+ char string>
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...docker compose up --build| Service | URL |
|---|---|
| API (via Nginx) | http://localhost |
| API (direct) | http://localhost:8000 |
| Interactive docs | http://localhost:8000/docs |
| ReDoc | http://localhost:8000/redoc |
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# Start Postgres and Redis (e.g. via homebrew or docker run)
uvicorn app.main:app --reload --port 8000Tests use SQLite in-memory β no external services required.
pip install -r requirements.txt aiosqlite
pytest tests/ -v --cov=app --cov-report=term-missingThe CI pipeline runs the full test suite against Python 3.11 and 3.12.
ecommerce-api/
βββ app/
β βββ main.py # Application factory, middleware, routers
β βββ config.py # Pydantic Settings (reads .env)
β βββ database.py # Async SQLAlchemy engine + Redis client
β βββ models/
β β βββ user.py # User (role, address, Stripe customer ID)
β β βββ product.py # Product + Category (hierarchical)
β β βββ cart.py # Cart + CartItem
β β βββ order.py # Order + OrderItem (denormalised snapshot)
β βββ schemas/
β β βββ product.py # ProductCreate/Update/Response + pagination
β β βββ cart.py # Cart request/response schemas
β β βββ order.py # Order placement + status schemas
β βββ routers/
β β βββ products.py # Catalog endpoints + category CRUD
β β βββ cart.py # Cart management
β β βββ orders.py # Order flow + Stripe webhook
β β βββ admin.py # Admin CRUD + dashboard metrics
β βββ services/
β β βββ payment.py # Stripe PaymentIntent abstraction
β β βββ inventory.py # Stock reservation & deduction
β βββ middleware/
β βββ logging.py # Structured request logging + X-Request-ID
βββ tests/
β βββ conftest.py # Fixtures, in-memory DB, Redis mock
β βββ test_products.py # Product catalog + admin CRUD tests
β βββ test_orders.py # Order flow + webhook tests
βββ nginx/
β βββ nginx.conf # Reverse proxy, rate limiting, security headers
βββ scripts/
β βββ init-db.sql # PostgreSQL extensions (pg_trgm, uuid-ossp)
βββ .github/workflows/
β βββ ci.yml # Lint β Test β Security β Docker build β Publish
βββ Dockerfile # Multi-stage build (builder β production)
βββ docker-compose.yml # App + PostgreSQL 16 + Redis 7 + Nginx
βββ requirements.txt
βββ .env.example
βββ README.md
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | postgresql+asyncpg://user:pass@host:5432/db |
REDIS_URL |
Yes | redis://host:6379/0 |
SECRET_KEY |
Yes | JWT signing key β 32+ random chars |
STRIPE_SECRET_KEY |
Yes | sk_test_... or sk_live_... |
STRIPE_WEBHOOK_SECRET |
Yes | whsec_... from Stripe dashboard |
APP_ENV |
No | development (default) or production |
DEBUG |
No | true enables SQL echo and verbose logging |
SENTRY_DSN |
No | Sentry project DSN for error tracking |
RATE_LIMIT_PER_MINUTE |
No | Default 60 |
See .env.example for the complete list.
Frontend API Stripe
β β β
β POST /api/v1/orders β β
β βββββββββββββββββββββ> β Validate cart + stock β
β β Deduct inventory β
β β Create Order row β
β β PaymentIntent.create() ββ> β
β β <ββββββββββββ client_secret β
β <βββββββββββββββββββββ β β
β {client_secret, ...} β β
β β β
β confirmCardPayment() β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββ> β
β β β
β β <ββ webhook: succeeded ββ β
β β Order β CONFIRMED/PAID β
- Database migrations β use Alembic.
create_all_tables()is only called automatically in non-production environments. - TLS β the Nginx config includes a commented-out HTTPS server block. Point your certificates at
/etc/nginx/certs/. - Scaling β stateless app containers can be horizontally scaled behind Nginx upstream. Redis handles shared session/reservation state.
- Admin security β the
/api/v1/admin/*prefix can be blocked at the Nginx layer for non-internal IPs. Role-based JWT guards should be added when the auth module is wired in.
MIT Β© 2025 shahabRDZ
Have ideas or experience to share? Check out our open discussions:
We'd love to hear your thoughts!