DEV Community

Anna lilith
Anna lilith

Posted on

How to Build a SaaS Product with Python in 2026

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
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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
    }
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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)