Skip to main content

Production-ready multi-tenant SaaS toolkit for FastHTML applications with authentication, billing, and integrations

Project description

🚀 fh-saas

⚡ Quick Start

1. Install

pip install fh-saas

2. Configure Environment

# .env file
DB_TYPE=POSTGRESQL
DB_USER=postgres
DB_PASS=your_password
DB_HOST=localhost
DB_NAME=app_host

# Optional integrations
STRIPE_SECRET_KEY=sk_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_MONTHLY_PRICE_ID=price_...
STRIPE_YEARLY_PRICE_ID=price_...
STRIPE_BASE_URL=https://yourapp.com
RESEND_API_KEY=re_...
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...

3. Initialize Your App

from fh_saas.db_host import HostDatabase, GlobalUser, TenantCatalog, Membership, gen_id, timestamp
from fh_saas.db_tenant import get_or_create_tenant_db
from fh_saas.utils_db import register_tables, create_indexes
from fh_saas.utils_log import configure_logging

# Configure logging (once at startup)
configure_logging()

# Connect to host database
host_db = HostDatabase.from_env()

# Create a new user
user = GlobalUser(
    id=gen_id(),
    email="founder@startup.com",
    oauth_id="google_abc123",
    created_at=timestamp()
)
host_db.global_users.insert(user)

# Create a tenant for their organization
tenant = TenantCatalog(
    id=gen_id(),
    name="Acme Corp",
    db_url="postgresql://...",
    created_at=timestamp()
)
host_db.tenant_catalogs.insert(tenant)

# Link user to tenant as owner
membership = Membership(
    id=gen_id(),
    user_id=user.id,
    tenant_id=tenant.id,
    profile_id=gen_id(),
    role="owner",
    created_at=timestamp()
)
host_db.memberships.insert(membership)
host_db.commit()

4. Work with Tenant Data

# Get tenant's isolated database
tenant_db = get_or_create_tenant_db(tenant.id, tenant.name)

# Define your app's data models
class Project:
    id: str
    name: str
    status: str = "active"
    created_at: str

class Task:
    id: str
    project_id: str
    title: str
    completed: bool = False

# Register tables (creates if not exist)
tables = register_tables(tenant_db, [
    (Project, "projects", "id"),
    (Task, "tasks", "id"),
])

# Add indexes for performance
create_indexes(tenant_db, [
    ("tasks", ["project_id"], False, None),
    ("tasks", ["completed"], False, None),
])

# Use the tables
projects = tables["projects"]
projects.insert(Project(id=gen_id(), name="Launch MVP", created_at=timestamp()))
tenant_db.conn.commit()

📚 Documentation Guide

Section What You'll Learn
📦 Core
Multi-Tenant Setup Host database, user management, tenant registry
Tenant Databases Isolated databases per tenant, connection pooling
Table Management Create tables & indexes from dataclasses
🔌 Integrations
HTTP Client REST APIs with retries, rate limiting, auth
GraphQL Client Streaming pagination for large datasets
Webhooks Receive & verify external webhooks
💳 Payments & Migrations
Billing Orchestrator Provider-agnostic billing contracts, lifecycle services, one-liner route registration
Stripe Payments Subscriptions, trials, access control
DB Migrations Version tracking, rollback support
⚙️ Data Pipeline
Background Tasks Async job execution
Data Transforms JSON → Polars → Database pipeline
🛠️ Utilities
SQL Helpers Database type detection, query builders
Logging Configurable logging for all modules
Authentication OAuth flows (Google, GitHub)
Email Sending Transactional emails via Resend
📣 Content
Blog Publishing Markdown blog with frontmatter
SEO & Sitemaps Sitemap generation, meta tags
Workflow Engine Multi-step automation

⚡ Performance Notes

For FastHTML and Starlette apps with a mix of DB-backed and non-DB protected pages, use auth hydration and tenant DB access as separate steps:

from fh_saas.utils_auth import create_auth_beforeware, require_tenant_access

beforeware = create_auth_beforeware(
    setup_tenant_db=False,
    session_cache=True,
)

@app.get('/dashboard')
def dashboard(request):
    return render_dashboard(request.state.user)

@app.get('/transactions')
def transactions(request):
    tenant_db = require_tenant_access(request)
    tables = get_app_tables(tenant_db)
    return render_transactions(tables)

This keeps protected non-DB pages from paying tenant DB setup costs on every request while still preserving a lazy boundary for routes that actually need tenant data. For rollout diagnostics, set FH_TIMING_ENABLED=1 to emit timing labels for auth and tenant DB setup.


📝 Release Notes

v0.9.13

Before After Benefit
Auth beforeware eagerly opened the tenant DB on every protected request, even for pages that only needed session or user info. Auth hydration and tenant DB access were split, so non-DB routes can stop at request.state.user, and DB routes cross the boundary only when they call require_tenant_access. Lower latency on protected pages like dashboards, nav loads, and HTMX partials that do not query tenant tables.
Every tenant DB open could pay engine setup and schema reflection costs again. Tenant routing, shared SQLAlchemy engine, and reflected metadata are cached, while each request still gets a fresh live connection. Warm tenant routes become much cheaper after the first hit, especially for repeated requests within the same tenant.
Role lookup and membership validation did broader reads and more Python-side filtering than necessary. Role resolution, global user lookup, membership lookup, and membership verification now use direct where ... limit 1 queries. Faster auth decisions, less DB I/O, and less Python work on login and route protection paths.
Session-heavy HTMX traffic could rebuild the same auth state on each request. Optional session auth caching reuses a recent hydrated auth payload for a short TTL. Reduced repeated auth cost on bursty UI interactions and partial refreshes.
Billing middleware and checkout return paths could fetch the same subscription more than once in one request flow. Trial initialization returns the resolved subscription, and checkout and billing hooks reuse already-fetched subscription state. Fewer duplicate host DB reads on subscription-gated routes and payment return flows.
Route timing required ad hoc logging to understand where latency was going. Env-gated timing hooks emit labeled timings for auth and tenant DB phases when enabled. Easier rollout diagnostics and faster identification of remaining bottlenecks.

🛠️ Developer Guide

This project uses nbdev for literate programming. The source of truth is in the nbs/ notebooks.

Development Setup

# Clone and install in dev mode
git clone https://github.com/abhisheksreesaila/fh-saas.git
cd fh-saas
pip install -e .

# Make changes in nbs/ directory, then compile
nbdev_prepare

📦 Installation

# From PyPI (recommended)
pip install fh-saas

# From GitHub (latest)
pip install git+https://github.com/abhisheksreesaila/fh-saas.git

GitHub · PyPI · Documentation

🤖 For AI Assistants: Download llms-ctx.txt — Complete API documentation for LLMs


🤖 AI Assistant Context

For AI coding assistants (GitHub Copilot, Claude, ChatGPT, Cursor, etc.), download the complete API documentation:

📥 Download llms-ctx.txt — Complete fh-saas documentation in LLM-friendly format

How to use:

  1. Download the context file
  2. Add to your AI assistant’s instructions/knowledge base
  3. Get accurate code suggestions for fh-saas APIs

This file contains all module documentation, examples, and API signatures formatted for optimal LLM understanding.


🤝 Contributing

Contributions are welcome! Please check the GitHub repository for issues and discussions.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fh_saas-0.9.14.tar.gz (76.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fh_saas-0.9.14-py3-none-any.whl (80.2 kB view details)

Uploaded Python 3

File details

Details for the file fh_saas-0.9.14.tar.gz.

File metadata

  • Download URL: fh_saas-0.9.14.tar.gz
  • Upload date:
  • Size: 76.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for fh_saas-0.9.14.tar.gz
Algorithm Hash digest
SHA256 9a545189557643391b907e150186e5a82fa7be476dd457789a6a5fb9f14f20f4
MD5 f619414c964e0f69cfcb01275bafa25d
BLAKE2b-256 ad03ccee20b84b8e408296bf2281f4cdc0a418e873cf8de986b212aaa2db20a8

See more details on using hashes here.

File details

Details for the file fh_saas-0.9.14-py3-none-any.whl.

File metadata

  • Download URL: fh_saas-0.9.14-py3-none-any.whl
  • Upload date:
  • Size: 80.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for fh_saas-0.9.14-py3-none-any.whl
Algorithm Hash digest
SHA256 762a52d4cdc161782270b87d27551b8e3954735329e79c80d8979b6915ff4831
MD5 5a3965f162b8aaf3a3182a4aef78b124
BLAKE2b-256 368b9511aa600f1e7cff03df3cc3e826807172e7569fd3a75a6e92dab0294af3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page