Building Python Web Apps logo

Building Python Web Apps

Community
wdm0006
building-python-web-apps

Provides an opinionated reference architecture for production Python web apps — FastAPI + async SQLAlchemy + Postgres, pydantic-settings config, Stripe billing, server-rendered Jinja or a decoupled SPA, deployed as a single Docker image on a managed platform via Terraform. Use when starting a new SaaS/web app, choosing a stack, structuring routers/services/models, wiring Stripe webhooks, protecting FastAPI docs or OpenAPI routes, setting up config/secrets, or deciding how to deploy.

Overview

Publisherwdm0006
Repositorypython-skills
Skill namebuilding-python-web-apps
Stars
111
Forks
17
Bundled files
6
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 6 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by wdm0006 on GitHub. Read the source before you install it.

Installation

Install the Building Python Web Apps AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/wdm0006/python-skills/tree/main/skills/python/web-app-architecture
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/wdm0006/python-skills.git /tmp/python-skills
mkdir -p .claude/skills
cp -r /tmp/python-skills/skills/python/web-app-architecture .claude/skills/building-python-web-apps
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Building Python Web Apps in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Building Python Web Apps on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Building Python Web Apps is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Building Python Web Apps

An opinionated, batteries-included blueprint for shipping a small-to-mid SaaS app solo or with a small team. It favors boring, well-understood pieces wired the same way every time so a new app reaches "auth + billing + deploy" in hours, not weeks.

The Default Stack

ConcernDefault
FrameworkFastAPI (async), uvicorn[standard]
Packaginguv + pyproject.toml + uv.lock, hatchling, src/ layout
DatabasePostgres 16 + asyncpg, SQLAlchemy 2.0 async, Alembic
Configpydantic-settings BaseSettings, env-prefixed, @lru_cache
PaymentsStripe ≥15, one signed webhook, idempotency table
Authdependency guards + bcrypt; token scheme per app (see AUTH.md)
FrontendJinja2 + Tailwind (CDN) + Alpine.js, or a decoupled SPA
Background workcron-as-worker (same image, different command) — no Celery
ObservabilitySentry + PostHog, both no-op until keyed
DeployDocker → managed platform (Render-style) via Terraform
Lint/testruff + pytest + pytest-asyncio, Makefile entrypoint

If you have no reason to deviate, use every row. The rest of this skill is the "how", and the reference files hold copy-pasteable templates.

Package Layout

One layered package, routers split by domain. Business logic never lives in a route.

src/myapp/
  main.py            # app + middleware + include_router() in a loop
  config.py          # pydantic-settings Settings + get_settings()
  db.py              # lazy async engine/sessionmaker + get_session() dep
  models.py          # SQLAlchemy 2.0 DeclarativeBase, Mapped[]
  schemas.py         # Pydantic request/response models
  security.py        # hashing, tokens, auth dependencies
  observability.py   # Sentry/PostHog init (guarded)
  api/               # one APIRouter module per domain (+ deps.py, webhooks.py)
  services/          # business logic; external SDKs isolated here
migrations/versions/ # Alembic
tests/               # pytest, mirrors api/ + services/
terraform/           # render.tf, stripe.tf, posthog.tf, variables.tf
Dockerfile  docker-compose.yml  pyproject.toml  uv.lock  alembic.ini  Makefile

Routes → Services → Models. Routes parse/validate and call services. Services hold the logic and own all external SDK calls (Stripe, email, LLM) so they're mockable. Models are persistence only.

App Wiring

A module-level app (a factory is fine but unnecessary), routers registered in a loop, cross-cutting concerns as middleware, and a health check that proves the DB is reachable. Full template in STACK.md.

python
# main.py (shape)
app = FastAPI(docs_url=None if settings.is_prod else "/docs")
app.add_middleware(CORSMiddleware, allow_origins=settings.cors_origins, ...)
# request-id + security-headers middleware, slowapi rate limiter
for router in (auth.router, billing.router, webhooks.router, ...):
    app.include_router(router)

@app.get("/health")
async def health(db: SessionDep):
    await db.execute(text("SELECT 1"))   # 503 if the DB is down
    return {"status": "ok"}

Share dependencies as Annotated type aliases — they read cleanly in signatures:

python
# api/deps.py
SessionDep = Annotated[AsyncSession, Depends(get_session)]
UserDep    = Annotated[User, Depends(require_user)]

Database

Lazy singleton async engine, pool_pre_ping=True, expire_on_commit=False, a get_session() async-generator dependency, and a URL normalizer so platform-issued postgres:// URLs become postgresql+asyncpg://. Postgres in prod, SQLite (aiosqlite) for fast local/CI tests. Migrations via Alembic, applied in the deploy's pre-deploy step. See STACK.md.

Config & Secrets

One Settings(BaseSettings) with an env prefix, @lru_cache get_settings(), and validators that refuse to boot in production with a default/missing secret. Commit a .env.example; never commit .env. Computed @property helpers (is_prod, cors_origins, billing_enabled) keep routes clean.

Payments

Stripe with exactly one signature-verified webhook endpoint, idempotency backed by a persisted events table, and dispatch by event type that never 500s on an unknown event. Isolate every SDK call in a services/stripe/ module. Full patterns (checkout, subscriptions, webhook handler, Terraform-provisioned signing secret) in PAYMENTS.md.

The Decisions You Actually Make

Everything above is fixed. These are the real per-app choices:

  • Frontend — server-rendered Jinja + Tailwind/Alpine (zero JS build, ship fast) or a decoupled React/Vite SPA on its own origin (richer UI, more moving parts). → FRONTEND.md
  • Auth scheme — cookie-JWT (browser app), bearer-JWT/API-key (programmatic), magic-link (passwordless), or OAuth (delegated). bcrypt + dependency guards are constant; only the token differs. → AUTH.md
  • Background work — inline in the request (simplest), cron-as-worker (the default for periodic/queued work), or a Redis-backed job queue (long-poll jobs). Reach for Celery only when you've outgrown all three. → BACKGROUND_JOBS.md
  • LLM features — pydantic-ai with structured Pydantic outputs, key required at boot, model configurable via env. → FRONTEND.md has the note.

Deployment

Single Docker image (the astral-sh/uv base, two-stage uv sync for layer caching, non-root, uvicorn --proxy-headers). The same image runs web, worker, and migrations via different commands. Target a managed platform; provision it with Terraform (app + Postgres + Stripe + PostHog as code), with a platform Blueprint file as the documented fallback. Migrations run in the pre-deploy step. Secrets are generated in Terraform or marked no-sync and set out of band. → DEPLOYMENT.md

Review Checklist

Structure:
- [ ] Logic in services/, not in route handlers
- [ ] External SDKs (Stripe/email/LLM) isolated behind a service module
- [ ] /health (or /readyz) actually pings the database
- [ ] Job status: failure writes its own terminal value, "in progress" is written
      before the work is handed off, and the current value is read by monotonic id

Data:
- [ ] Async engine is a lazy singleton with pool_pre_ping
- [ ] postgres:// URLs normalized to postgresql+asyncpg://
- [ ] Every schema change has an Alembic migration

Config:
- [ ] Production refuses to boot on a default/missing secret
- [ ] CORS not "*" and HTTPS enforced in production
- [ ] .env.example committed; .env gitignored

Billing & security:
- [ ] Exactly one Stripe webhook, signature-verified, idempotent
- [ ] New passwords centrally limited to 72 UTF-8 bytes before bcrypt hashing
- [ ] Password verification remains unbounded for compatibility with existing hashes
- [ ] Passwords bcrypt-hashed; auth via reusable dependencies
- [ ] CSRF enforced globally for cookie auth (not per-form opt-in); no
      state-changing GET requests — mutations use POST/DELETE
- [ ] Client IP for rate-limit keys/allowlists/audit logs taken from a fixed
      position in the X-Forwarded-For chain (not the spoofable left-most value)
- [ ] Authentication rate limits use a required shared backend; backend outages
      fail closed with 503, while healthy exhausted buckets return 429 + actual TTL
- [ ] Private OpenAPI schema served only by a custom guarded route (`openapi_url=None`)
- [ ] Sentry + PostHog init guarded (no-op when unkeyed, never break a request)

Ship:
- [ ] One Docker image; web/worker/migrate are just different commands
- [ ] Migrations run pre-deploy, not at app startup in prod
- [ ] Infra is in Terraform; secrets are not committed

Reference Files

  • STACK.md — pyproject, main.py, db.py, config.py, Makefile templates
  • PAYMENTS.md — Stripe checkout + idempotent webhook patterns
  • AUTH.md — the four token schemes and shared dependency guards
  • BACKGROUND_JOBS.md — inline vs cron-worker vs Redis queue
  • FRONTEND.md — Jinja/Tailwind/Alpine vs decoupled SPA (+ LLM note)
  • DEPLOYMENT.md — Dockerfile, Terraform, migrations, secrets

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Building Python Web Apps AI skill do?

Provides an opinionated reference architecture for production Python web apps — FastAPI + async SQLAlchemy + Postgres, pydantic-settings config, Stripe billing, server-rendered Jinja or a decoupled SPA, deployed as a single Docker image on a managed platform via Terraform. Use when starting a new SaaS/web app, choosing a stack, structuring routers/services/models, wiring Stripe webhooks, protecting FastAPI docs or OpenAPI routes, setting up config/secrets, or deciding how to deploy.

Why use Building Python Web Apps on TypingMind?

Because you install it once and use it with any model. Building Python Web Apps is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Building Python Web Apps in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/wdm0006/python-skills/tree/main/skills/python/web-app-architecture. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Building Python Web Apps?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Building Python Web Apps?

As many as you like. As long as a model supports skills, you can use Building Python Web Apps with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Building Python Web Apps AI skill free?

Yes. It is published on GitHub by wdm0006 under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇