██████╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ██████╗
██╔══██╗ ██║██╔══██╗████╗ ██║██╔════╝ ██╔═══██╗
██║ ██║ ██║███████║██╔██╗ ██║██║ ███╗██║ ██║
██║ ██║██ ██║██╔══██║██║╚██╗██║██║ ██║██║ ██║
██████╔╝╚█████╔╝██║ ██║██║ ╚████║╚██████╔╝╚██████╔╝
╚═════╝ ╚════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝
S a a S K i t
Ship your multi-tenant SaaS product in days, not months.
Django SaaS Kit is a production-ready backend template for teams who want to skip the boilerplate and start building features on day one.
It gives you the hard parts out of the box — multi-tenant routing, JWT auth with rotation, RBAC, async tasks, real-time notifications, GDPR tooling, structured logging — all wired together and tested.
Your idea ──► clone ──► make dev ──► ship features
(5 sec) (2 min) (today)
Requires: Docker + Docker Compose (Docker Desktop includes both)
# 1. Clone
git clone https://github.com/abu-rayhan-alif/django-saas-kit.git
cd django-saas-kit
# 2. Boot the full stack (DB + Redis + Celery + web)
make dev
# 3. Open the interactive API docs
open http://localhost:8000/api/docs/make dev handles everything: copies .env, builds images, runs migrations, starts all services.
No make?
cp .env.example .env && docker compose up -d --buildWithout Docker (virtualenv)
python -m venv .venv
source .venv/bin/activate # Linux / macOS
# .venv\Scripts\activate # Windows
pip install -r requirements/local.txt
cp .env.example .env
python manage.py migrate
python manage.py runserver| Service | URL | Notes |
|---|---|---|
| Swagger UI | localhost:8000/api/docs/ | Try every endpoint interactively |
| ReDoc | localhost:8000/api/redoc/ | Clean reference view |
| Health | localhost:8000/health/ | Load-balancer probe |
| Admin | localhost:8000/admin/ | Django admin |
|
|
┌─────────────────────────────────────────────────────────────┐
│ HTTP Request │
└───────────────────────────┬─────────────────────────────────┘
│
┌─────────────▼─────────────┐
│ MIDDLEWARE STACK │
│ SecurityMiddleware │
│ RequestContextMiddleware │ ← binds request_id
│ TenantMiddleware │ ← resolves tenant
│ AuthenticationMiddleware │ ← verifies JWT
└─────────────┬─────────────┘
│
┌─────────────▼─────────────┐
│ VIEW (thin adapter) │
│ validate input │
│ call service │
│ map result → HTTP │
└─────────────┬─────────────┘
│
┌─────────────▼─────────────┐
│ SERVICE LAYER (no HTTP) │
│ business rules │
│ validation │
│ orchestration │
└─────────────┬─────────────┘
│
┌─────────────▼─────────────┐
│ MODELS / POSTGRES │
│ UUID PKs, soft delete │
│ audit fields │
└────────────────────────────┘
| Layer | Location | Rule |
|---|---|---|
| Views / serializers | apps/*/views.py |
HTTP only — no business logic |
| Services | services/ |
No HTTP imports |
| Models | apps/*/models.py |
Schema + DB constraints |
| RBAC | apps/rbac/ |
Tenant-scoped role checks |
→ Full design: docs/architecture/service-layer.md
django-saas-kit/
├── apps/
│ ├── authentication/ # JWT, password reset, registration, OAuth, 2FA
│ ├── billing/ # Stripe plans, subscriptions, webhook handler
│ ├── common/ # Pagination, exceptions, middleware, throttling
│ ├── features/ # Per-tenant feature flags (waffle + overrides)
│ ├── invitations/ # Token-based team invitations
│ ├── notifications/ # WebSocket + DB notifications
│ ├── audit/ # Immutable audit log
│ ├── rbac/ # Roles, permissions
│ ├── tenants/ # Tenant model, Domain model, middleware
│ └── users/ # User profile, avatar, tasks
│
├── services/ # Pure business logic (no HTTP)
│ ├── auth/ # Password reset, social auth service
│ ├── features/ # FeatureService — flag resolution
│ ├── notifications/ # Notification service
│ ├── rbac/ # RBAC service
│ ├── tenants/ # TenantService.create_tenant()
│ └── users/ # UserService, GDPRService
│
├── config/
│ ├── settings/
│ │ ├── base.py # Shared settings
│ │ ├── local.py # Dev overrides
│ │ └── prod.py # Production
│ ├── urls.py
│ └── celery.py
│
├── tests/
│ ├── unit/ # Fast, no-DB tests
│ └── integration/ # Full-stack endpoint tests
│
└── docs/
├── adr/ # Architecture decision records
├── architecture/ # Design docs
└── gdpr.md # Privacy & compliance
All endpoints live under /api/v1/. Authenticate with Authorization: Bearer <access_token>.
| Resource | Base path | Key endpoints |
|---|---|---|
| Auth | /api/v1/auth/ |
POST /token/ · POST /register/ · POST /social/ |
| 2FA | /api/v1/auth/2fa/ |
GET /setup/ · POST /enable/ · POST /complete/ |
| Users | /api/v1/users/ |
GET /me/ · PATCH /me/profile/ |
| Tenants | /api/v1/tenants/ |
GET / · POST / |
| RBAC | /api/v1/rbac/<tenant_id>/ |
GET /roles/ · POST /roles/assign/ |
| Notifications | /api/v1/notifications/ |
GET / · PATCH /<id>/read/ |
| Billing | /api/v1/billing/ |
POST /webhook/ |
| Features | /api/v1/features/ |
GET / · GET /<flag_name>/ |
Get a token in 3 lines:
curl -s -X POST http://tenant1.localhost:8000/api/v1/auth/token/ \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"yourpassword"}' | jq .accessSocial login (Google / GitHub):
curl -s -X POST http://tenant1.localhost:8000/api/v1/auth/social/ \
-H "Content-Type: application/json" \
-d '{"provider": "google", "access_token": "<oauth_token>"}'→ Full interactive docs: localhost:8000/api/docs/
Enable TOTP 2FA for any user account (Google Authenticator, Authy, etc.):
# 1. Get setup QR code (authenticated)
GET /api/v1/auth/2fa/setup/
# → returns secret + provisioning_uri + qr_svg
# 2. Scan QR in your authenticator app, then verify the first code
POST /api/v1/auth/2fa/enable/ {"code": "123456"}
# → returns backup_codes (save these!)
# 3. On next login, if 2FA is enabled the token endpoint returns:
# {"two_fa_required": true, "session_key": "..."}
# Complete login with:
POST /api/v1/auth/2fa/complete/ {"session_key": "...", "code": "123456"}
# → returns normal JWT pairThe OAuth flow is frontend-initiated — your client handles the redirect and callback, then hands the access token to the API:
User clicks "Login with Google"
→ Frontend redirects to Google OAuth consent
→ Google redirects back with code
→ Frontend exchanges code for access_token
→ POST /api/v1/auth/social/ {"provider":"google","access_token":"..."}
→ API returns JWT pair (creates account on first login)
Supported providers: google, github.
Feature flags combine global django-waffle switches with per-tenant overrides:
from services.features import FeatureService
# In a view — reads tenant from request automatically
if FeatureService.is_enabled("advanced_analytics", request=request):
...
# In a service / task
if FeatureService.is_enabled("bulk_export", tenant=tenant):
...
# Check all flags at once (used by the frontend)
# GET /api/v1/features/
# → {"advanced_analytics": true, "bulk_export": false, ...}Per-plan flags are configured in settings and synced automatically on subscription changes:
# config/settings/base.py
PLAN_FEATURE_FLAGS = {
"free": {"api_access": False, "advanced_analytics": False},
"starter": {"api_access": True, "advanced_analytics": False},
"pro": {"api_access": True, "advanced_analytics": True},
"enterprise": {"api_access": True, "advanced_analytics": True, "sso": True},
}Admin overrides live in Django Admin under Tenant Feature Flags.
Add TenantPlanThrottle to any view to enforce subscription-tier rate limits:
from apps.common.throttling import TenantPlanThrottle
class MyView(APIView):
throttle_classes = [TenantPlanThrottle]Subdomains route requests to the correct tenant automatically.
1. Add hosts (one-time)
# /etc/hosts (Linux/macOS)
# C:\Windows\System32\drivers\etc\hosts (Windows)
127.0.0.1 tenant1.localhost
127.0.0.1 tenant2.localhost
2. Create tenants
# python manage.py shell
from services.tenants import TenantService
TenantService.create_tenant("Acme Corp", "acme", "acme.localhost")
TenantService.create_tenant("Beta Ltd", "beta", "beta.localhost")3. Make requests
# Hits Acme's workspace
curl http://acme.localhost:8000/api/v1/auth/token/ ...
# Hits Beta's workspace
curl http://beta.localhost:8000/api/v1/auth/token/ ...→ Architecture explained: docs/architecture/tenancy.md
cp .env.example .env # then edit as needed| Variable | Required | Default | Purpose |
|---|---|---|---|
SECRET_KEY |
Yes | — | Django signing key |
DATABASE_URL |
Yes | postgres://...@db:5432/saas_db |
PostgreSQL |
REDIS_URL |
Yes | redis://redis:6379/0 |
Cache + Celery |
DEBUG |
False |
Dev mode | |
ALLOWED_HOSTS |
Prod | — | Comma-separated hostnames |
JWT_ACCESS_TOKEN_LIFETIME_MINUTES |
15 |
Access token TTL | |
JWT_REFRESH_TOKEN_LIFETIME_DAYS |
7 |
Refresh token TTL | |
EMAIL_BACKEND |
console |
Swap for SMTP in prod | |
DEFAULT_FROM_EMAIL |
noreply@example.com |
From address | |
FRONTEND_URL |
http://localhost:3000 |
Password-reset & billing links | |
BILLING_PORTAL_URL |
$FRONTEND_URL/billing |
Billing email CTA | |
SITE_NAME |
Django SaaS Kit |
Email branding | |
STRIPE_SECRET_KEY |
Billing | — | Stripe API key |
STRIPE_WEBHOOK_SECRET |
Billing | — | Webhook signature verification |
THROTTLE_PLAN_FREE |
60/minute |
Rate limit for free plan | |
THROTTLE_PLAN_STARTER |
300/minute |
Rate limit for starter plan | |
THROTTLE_PLAN_PRO |
1000/minute |
Rate limit for pro plan | |
THROTTLE_PLAN_ENTERPRISE |
10000/minute |
Rate limit for enterprise plan | |
SENTRY_DSN |
Prod | — | Error tracking |
# Development
make dev # first-time setup (copy .env + build + start)
make up # start stack
make down # stop stack
make logs # tail web logs
make shell # Django shell inside container
# Code quality
make lint # ruff + mypy
make format # ruff format + auto-fix
make test # pytest (no Docker needed)
# Database
make migrate # run migrations
docker compose exec web python manage.py seed_demo # seed demo data
docker compose exec web python manage.py check_redis # verify Redis
# GDPR
docker compose exec web python manage.py delete_user_data <user_id>
docker compose exec web python manage.py delete_user_data <user_id> --hard-deleteErase a user's personal data in one command:
# Anonymize (default) — wipes PII, keeps row for audit integrity
docker compose exec web python manage.py delete_user_data <user_id>
# Hard-delete — removes the row entirely
docker compose exec web python manage.py delete_user_data <user_id> --hard-delete
# Scripted (no prompt)
docker compose exec web python manage.py delete_user_data <user_id> --no-inputWhat gets erased: username · email · name · password · profile · all notifications · all roles · all JWT tokens.
→ Policy + FK table: docs/gdpr.md
| Layer | Technology |
|---|---|
| Framework | Django 5 + Django REST Framework |
| Auth | djangorestframework-simplejwt + pyotp (TOTP 2FA) |
| Social Auth | Custom OAuth token exchange (Google, GitHub) |
| Database | PostgreSQL 16 |
| Cache / Broker | Redis 7 |
| Async tasks | Celery + Celery Beat |
| Real-time | Django Channels (WebSocket) |
| API docs | drf-spectacular (OpenAPI 3) |
| Logging | structlog (JSON in prod, console in dev) |
| Feature flags | django-waffle + per-tenant overrides |
| Billing | Stripe (webhooks, subscriptions, dunning) |
| File storage | Local filesystem / S3-compatible (MinIO in dev) |
| Error tracking | Sentry (optional, gate with SENTRY_DSN) |
| Linting | ruff + mypy |
| CI | GitHub Actions |
| Containerisation | Docker + Docker Compose |
GitHub Actions runs on every push to main and every PR:
push / PR
│
├── lint ruff check . && mypy .
├── test pytest (PostgreSQL + Redis service containers)
└── build docker build --target runtime
Configuration: .github/workflows/ci.yml
Contributions are welcome — bug fixes, features, docs, and ADRs.
git checkout -b feature/your-feature
# make changes
make lint && make test
git push origin feature/your-feature
# open a PR→ Read CONTRIBUTING.md and the PR template before opening a pull request.
MIT License · Copyright © 2026 Abu Rayhan Alif
If this saved you time, leave a ⭐ — it helps others find the project.