Skip to content

πŸ›’ Product marketplace

Multi-tenant sales platform Mercado Livre / Shopee style, no external integrations. The point is to exercise tempest-fastapi-sdk in a realistic scenario β€” auth, multi-tenant, RBAC, idempotency, auditable stock, orders with a state machine, real-time messaging (SSE + Web Push) for status and notifications, MinIO uploads, transactional email.

Project pages

Page What's there
Business rules The domain written as prose β€” who can do what, under which conditions. Read first.
Domain model UML class + ER diagrams, every entity explained (attributes, invariants, relationships).
Critical flows Sequence diagrams covering signup, member invite, product creation, checkout, shipment. Order and Invitation state machines.
Endpoint map Table of the whole REST API with method + path + auth + payload + status.

Project setup (5 min)

# 1. Scaffold via SDK
tempest new marketplace --extras auth,admin,upload,cache,email,minio,queue,tasks,metrics,prometheus,http,webpush

# 2. Boot the infra
cd marketplace
docker compose up -d

# 3. Configure env
cp .env.example .env
uv sync

# 4. Migrations (create the schema, including the users table)
uv run tempest db revision -m "init"
uv run tempest db upgrade

# 5. Bootstrap the first admin so /admin login works
uv run tempest user create --email admin@local --password admin-pass-12 --admin

# 6. Run
uv run python main.py

When the service boots you have:

  • REST API at http://localhost:8000
  • Swagger docs at http://localhost:8000/docs
  • SDK admin panel at http://localhost:8000/admin
  • MinIO console at http://localhost:9001 (minioadmin/minioadmin)
  • RabbitMQ UI at http://localhost:15672 (guest/guest)
  • MailHog UI at http://localhost:8025

Local-development credentials only

The logins and passwords above (minioadmin/minioadmin, guest/guest, the admin-pass-12 admin) are local-development defaults. Never deploy with them: rotate or replace every one with strong secrets before any shared or production environment.

SDK stack exercised

Need SDK primitive
Public signup + login + password reset UserAuthService + make_auth_router (signup / activate / login / password-reset ready since v0.31.0) + BaseUserModel + BaseUserTokenModel
Access-token refresh via JWT pair JWTUtils + make_jwt_user_dependency
Multi-tenant membership BaseRepository[T], make_role_dependency, make_permission_dependency
Member invitation EmailUtils.render_template + generate_opaque_token/hash_opaque_token
Variants + versioned prices BaseModel + AuditMixin + SQLAlchemy 2.0 async relationships
Append-only stock with bulk seed BaseModel (movement) + BaseRepository.bulk_create_values / bulk_upsert (v0.28+)
Non-duplicating checkout IdempotencyMiddleware + RedisIdempotencyStore
Upload body-size limit BodySizeLimitMiddleware (v0.28+)
Product images UploadUtils + MinIOUploadStorage + presigned URLs
Real-time order status (app open) SSEBroker β€” per-user channel (str(user.id)); broker.response(channel) builds the StreamingResponse
Notification with app closed (background) WebPushDispatcher + WebPushSubscriptionService + make_web_push_router ([webpush] extra)
One domain event on two channels (foreground + background) Custom NotificationService fanning out SSE + Web Push with the same payload
Async notifications AsyncTaskBrokerManager (TaskIQ) + AsyncBrokerManager (FastStream)
Public catalog cache AsyncRedisManager, @cached
Oncall metrics PrometheusMiddleware + make_prometheus_router (v0.28+) and MetricsUtils
Social OAuth (Google / GitHub) GoogleOAuthClient / GitHubOAuthClient / OIDCProvider (v0.29+)
Safe server-rendered forms CSRFMiddleware (v0.29+)
Outbound calls to external services HTTPClient (v0.28+) with retry + circuit-breaker
Standardized errors AppException hierarchy + register_exception_handlers
Per-level logs + /logs configure_logging(log_dir=…) + make_logs_router

Suggested structure

marketplace/
β”œβ”€β”€ main.py
β”œβ”€β”€ docker-compose.yaml
β”œβ”€β”€ pyproject.toml
β”œβ”€β”€ alembic/
└── src/
    β”œβ”€β”€ api/
    β”‚   β”œβ”€β”€ app.py
    β”‚   β”œβ”€β”€ routers/
    β”‚   β”‚   β”œβ”€β”€ auth.py            # signup + login + refresh
    β”‚   β”‚   β”œβ”€β”€ organizations.py   # CRUD org + members
    β”‚   β”‚   β”œβ”€β”€ invitations.py     # invite / accept / revoke
    β”‚   β”‚   β”œβ”€β”€ catalog.py         # public navigation
    β”‚   β”‚   β”œβ”€β”€ products.py        # product + variant CRUD (org-only)
    β”‚   β”‚   β”œβ”€β”€ stock.py           # in / out / adjust
    β”‚   β”‚   β”œβ”€β”€ cart.py            # buyer cart
    β”‚   β”‚   β”œβ”€β”€ orders.py          # checkout + status
    β”‚   β”‚   β”œβ”€β”€ reviews.py         # post-delivery reviews
    β”‚   β”‚   β”œβ”€β”€ notifications.py   # GET /notifications/stream (SSE)
    β”‚   β”‚   └── push.py            # make_web_push_router (subscriptions)
    β”‚   └── dependencies/
    β”‚       β”œβ”€β”€ auth.py            # current_user, require_org_role
    β”‚       └── controllers.py
    β”œβ”€β”€ controllers/               # cross-service orchestration
    β”œβ”€β”€ services/                  # domain logic
    β”‚   └── notification.py        # NotificationService (fan-out SSE + Web Push)
    β”œβ”€β”€ schemas/                   # Pydantic DTOs
    β”œβ”€β”€ core/                      # settings + exceptions + constants
    β”œβ”€β”€ db/
    β”‚   β”œβ”€β”€ models/                # ORM (includes push_subscription.py)
    β”‚   └── repositories/          # queries
    β”œβ”€β”€ queue/                     # FastStream consumers
    β”œβ”€β”€ tasks/                     # TaskIQ tasks
    └── utils/
  1. Auth β€” UserModel(BaseUserModel) + UserTokenModel(BaseUserTokenModel) + wire UserAuthService + make_auth_router. Five endpoints ready: /auth/signup, /auth/activate/{token}, /auth/login, /auth/password-reset/request, /auth/password-reset/confirm. (Covers: abstract model concretization, bundled flow, JWT pair, Jinja2 templates.) See Auth flow recipe Β».
  2. Organizations + members β€” Organization + Membership with the 2-orgs-per-user / 10-members-per-org guard. (Covers: domain invariants, role-based access.)
  3. Invitations β€” Invitation with opaque token, Jinja2 email, 7-day expiry. (Covers: transactional email.)
  4. Catalog + products β€” Product + ProductVariant + PriceHistory. (Covers: 1-N relationships, soft-delete.)
  5. Stock β€” StockMovement (append-only) + balance derived via view or aggregate query. (Covers: auditing, transactions.)
  6. Cart + checkout β€” Cart + Order + OrderItem with idempotency. (Covers: state machine, idempotency.)
  7. Real-time messaging (SSE + Web Push) β€” a NotificationService fans each domain event (order paid/shipped, invite, new review) out across two channels with the same payload: SSEBroker for the buyer with the app open, and Web Push (VAPID) for devices with the app closed. (Covers: SSE, Web Push.) SSE is core; Web Push needs uv add "tempest-fastapi-sdk[webpush]"; multi-worker SSE needs [cache]. See SSE recipe Β» and Web Push recipe Β».
  8. Product images β€” upload via UploadUtils + MinIOUploadStorage + listing with presigned URL. (Covers: storage.)
  9. Async notifications β€” TaskIQ sending emails + RabbitMQ publishing events to projections / reports. (Covers: queues, tasks.)
  10. Metrics + admin β€” Prometheus endpoint + /admin listing entities. (Covers: observability, SDK admin.)

Each step ships one complete vertical slice β€” one feature end-to-end before adding the next.