How to Build a SaaS Product with Python in 2026
Building a Software-as-a-Service product is one of the most profitable paths for Python developers. In this guide, you'll build a complete SaaS backend with authentication, billing, and deployment-ready infrastructure.
What You'll Build
A fully functional SaaS API with user registration, subscription billing via Stripe, API key authentication, and a usage-based pricing model. By the end, you'll have a production-ready foundation you can customize for any niche.
Why Python for SaaS?
Python offers distinct advantages for SaaS development:
- FastAPI delivers async performance rivaling Node.js
- Rich ecosystem of payment, auth, and database libraries
- Rapid prototyping — ship your MVP in days, not months
- Strong typing with Pydantic catches bugs before deployment
Full Tutorial
Step 1: Project Structure
saas-project/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ ├── models.py
│ ├── routes/
│ │ ├── auth.py
│ │ ├── billing.py
│ │ └── api.py
│ └── middleware.py
├── requirements.txt
├── Dockerfile
└── .env.example
Step 2: FastAPI Application Core
# app/main.py
from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from app.config import settings
from app.routes import auth, billing, api
app = FastAPI(
title="SaaS API",
description="Production-ready SaaS backend",
version="1.0.0"
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(auth.router, prefix="/auth", tags=["Authentication"])
app.include_router(billing.router, prefix="/billing", tags=["Billing"])
app.include_router(api.router, prefix="/api/v1", tags=["API"])
@app.get("/health")
async def health_check():
return {"status": "healthy", "version": "1.0.0"}
Step 3: Configuration Management
# app/config.py
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
DATABASE_URL: str
SECRET_KEY: str
STRIPE_SECRET_KEY: str
STRIPE_WEBHOOK_SECRET: str
ALLOWED_ORIGINS: list[str] = ["http://localhost:3000"]
JWT_ALGORITHM: str = "HS256"
JWT_EXPIRY_MINUTES: int = 60
class Config:
env_file = ".env"
@lru_cache()
def get_settings():
return Settings()
settings = get_settings()
Step 4: User Authentication
# app/routes/auth.py
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, EmailStr
import jwt
from datetime import datetime, timedelta
from passlib.context import CryptContext
from app.config import settings
router = APIRouter()
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
class UserRegister(BaseModel):
email: EmailStr
password: str
name: str
class UserLogin(BaseModel):
email: EmailStr
password: str
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
def create_token(user_id: str) -> str:
payload = {
"sub": user_id,
"exp": datetime.utcnow() + timedelta(minutes=settings.JWT_EXPIRY_MINUTES),
"iat": datetime.utcnow()
}
return jwt.encode(payload, settings.SECRET_KEY, algorithm=settings.JWT_ALGORITHM)
@router.post("/register", response_model=TokenResponse)
async def register(user: UserRegister):
hashed = pwd_context.hash(user.password)
user_id = "new_user_123" # Replace with database call
token = create_token(user_id)
return TokenResponse(access_token=token)
@router.post("/login", response_model=TokenResponse)
async def login(credentials: UserLogin):
# Verify credentials against database
user_id = "user_123" # Replace with database call
token = create_token(user_id)
return TokenResponse(access_token=token)
Step 5: Stripe Billing Integration
# app/routes/billing.py
from fastapi import APIRouter, Depends, Request
import stripe
from app.config import settings
from pydantic import BaseModel
router = APIRouter()
stripe.api_key = settings.STRIPE_SECRET_KEY
PLANS = {
"starter": {"price_id": "price_starter_monthly", "name": "Starter", "requests": 10000},
"pro": {"price_id": "price_pro_monthly", "name": "Pro", "requests": 100000},
"enterprise": {"price_id": "price_enterprise_monthly", "name": "Enterprise", "requests": 1000000},
}
class CheckoutRequest(BaseModel):
plan: str
email: str
@router.post("/create-checkout")
async def create_checkout(request: CheckoutRequest):
plan = PLANS.get(request.plan)
if not plan:
raise HTTPException(status_code=400, detail="Invalid plan")
session = stripe.checkout.Session.create(
mode="subscription",
payment_method_types=["card"],
line_items=[{
"price": plan["price_id"],
"quantity": 1
}],
customer_email=request.email,
success_url="https://yourapp.com/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url="https://yourapp.com/pricing",
metadata={"plan": request.plan}
)
return {"checkout_url": session.url}
@router.post("/webhook")
async def stripe_webhook(request: Request):
payload = await request.body()
sig_header = request.headers.get("stripe-signature")
event = stripe.Webhook.construct_event(
payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
)
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
# Activate user subscription
print(f"Subscription activated for {session['customer']}")
elif event["type"] == "invoice.payment_failed":
# Handle failed payment
pass
return {"received": True}
Step 6: API Key Authentication
# app/routes/api.py
from fastapi import APIRouter, Depends, HTTPException, Security
from fastapi.security import APIKeyHeader
router = APIRouter()
api_key_header = APIKeyHeader(name="X-API-Key")
async def verify_api_key(api_key: str = Security(api_key_header)):
# Validate against database
if not api_key or len(api_key) < 32:
raise HTTPException(status_code=401, detail="Invalid API key")
return api_key
@router.get("/data")
async def get_data(api_key: str = Depends(verify_api_key)):
return {
"status": "success",
"data": {"message": "Authenticated request completed"},
"usage_remaining": 9997
}
Step 7: Docker Deployment
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Scaling Considerations
- Use Redis for session caching and rate limiting
- Implement background task processing with Celery
- Add database connection pooling with SQLAlchemy async
- Set up monitoring with Prometheus and Grafana
Get the Code
Ready to use these tools? Browse our collection of tested, production-ready Python scripts:
🔗 Browse Products: Anna's Digital Products
All products include:
- ✅ Tested and verified code
- ✅ Instant delivery via crypto or card
- ✅ Free updates forever
- ✅ Telegram bot support (@AnnaLilithBot)
Top comments (0)