Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -1670,6 +1670,25 @@
"security",
"compliance"
]
},
{
"name": "app-starter",
"source": "./plugins/app-starter",
"description": "Bootstrap new Next.js, Flutter, and FastAPI apps with current packages, no deprecated APIs, and a consistent house style. Ships skills nextjs-app, flutter-app, and fastapi-app.",
"version": "0.2.0",
"author": {
"name": "Aneeb Baig"
},
"category": "Development Engineering",
"homepage": "https://github.com/aneebbaig/app-starter-skills",
"keywords": [
"nextjs",
"flutter",
"fastapi",
"scaffold",
"starter",
"bootstrap"
]
}
]
}
}
20 changes: 20 additions & 0 deletions plugins/app-starter/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "app-starter",
"description": "Bootstrap new Next.js, Flutter, and FastAPI apps with current packages, no deprecated APIs, and a consistent house style. Ships skills nextjs-app, flutter-app, and fastapi-app.",
"version": "0.2.0",
"author": {
"name": "Aneeb Baig",
"url": "https://github.com/aneebbaig"
},
"homepage": "https://github.com/aneebbaig/app-starter-skills",
"repository": "https://github.com/aneebbaig/app-starter-skills",
"license": "MIT",
"keywords": [
"nextjs",
"flutter",
"fastapi",
"scaffold",
"starter",
"bootstrap"
]
}
87 changes: 87 additions & 0 deletions plugins/app-starter/skills/fastapi-app/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
name: fastapi-app
description: Bootstrap a new FastAPI backend with async SQLAlchemy 2.0, asyncpg, Alembic, Pydantic v2, and no deprecated APIs. Use when the user wants to start, scaffold, or set up a new FastAPI service, a Python REST API, an async backend, or asks to "create a new fastapi app" or "new python backend". Handles JWT auth, layered app structure, Docker + Postgres, and Vercel or container deploy.
---

# fastapi-app

Bootstrap a new FastAPI backend the way this owner builds them: async SQLAlchemy
2.0 with asyncpg, Alembic migrations, Pydantic v2 settings, a layered structure
(routers, services, models, schemas), JWT auth, and the house git and CI
workflow. Deployable to a container or Vercel.

First read the shared rules (they override anything you remember):
`../shared/house-rules.md`, `../shared/no-ai-attribution.md`,
`../shared/git-and-ci.md`, `../shared/docs-and-context.md`,
`../shared/hardening.md`, and (for public repos) `../shared/open-source-docs.md`.

## Step 0. Get the brief, then ask the variant questions (hard stop)

This is a hard stop. Do not run any scaffolding command until the user has
answered.

First, get the project brief: one paragraph on what the service does, its main
resources and endpoints, who calls it, and any hard constraints. If the user has
not given one, ask for it. The brief drives naming, the domain modules, and the
data model.

Then ask the variant questions. If a choice has multiple options, ask; do not
assume. Ask in one batch, then proceed.

1. Repo visibility: private, open-source, or private-plus-open-source.
2. Auth: JWT (python-jose or PyJWT), OAuth (Google), API-key, or none yet.
3. Database: Postgres via async SQLAlchemy + asyncpg (default), or none yet.
4. Dependency tooling: `uv` (default, fast) or `pip` + `requirements.txt`.
5. Admin UI: SQLAdmin, or none.
6. Deploy target: Docker container (default) or Vercel serverless.

If the user already answered some, do not re-ask.

## Step 1. Verify environment and current versions

- Check Python (`python3 --version`, want a current supported 3.x).
- Run `scripts/check-latest.sh` for current stable versions from PyPI. Pin those,
not versions from memory (`../shared/house-rules.md` rule 2).
- Pull current FastAPI, SQLAlchemy 2.0, and Pydantic v2 docs via Context7 before
writing code (`../shared/docs-and-context.md`). SQLAlchemy 2.0 async and
Pydantic v2 both broke v1 patterns; do not write v1-era code from memory.

## Step 2. Scaffold the project

Create a virtualenv and the layout from `references/structure.md`. With `uv`:

```
uv init <name> && cd <name>
uv add fastapi "uvicorn[standard]" "sqlalchemy[asyncio]" asyncpg alembic \
pydantic-settings python-jose[cryptography] httpx python-multipart
uv add --dev ruff pytest pytest-asyncio
```

With pip, install the same set and freeze into `requirements.txt`. Let the tool
resolve current versions; do not force numbers you remember.

## Step 3. Apply structure and conventions

- Layered app structure, async DB session, dependency-injected DB, settings, JWT
auth: `references/structure.md`.
- Best practices, scalable domain-modular architecture, and nothing hardcoded:
`references/best-practices.md`.
- Dependency set and version-boundary notes: `references/stack.md`.
- Production hardening (docs and schema disabled or gated in prod, generic error
bodies, debug off, CORS locked): `../shared/hardening.md`.

## Step 4. Git, CI, docs, security

- Git branch model, conventional commits, auto-merge: `../shared/git-and-ci.md`.
- CI (ruff + pytest), Docker, migrations: `references/quality-gates.md`.
- Gitignore `.env*` and any service-account JSON. Provide `.env.example`.
Settings load from env through `pydantic-settings`, never hardcoded.
- Add `docs/` and a README. For a public repo, ship the full open-source docs set
per `../shared/open-source-docs.md` and run the open-source hard gate in
`../shared/no-ai-attribution.md` before the first push.

## Step 5. Verify before declaring done

Run the gates in `references/quality-gates.md`: `ruff check`, `pytest`, the app
imports and starts, `/health` responds, and Alembic can generate a revision.
Report real results.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# FastAPI best practices and scalable architecture

Read before writing endpoints. Confirm current FastAPI, SQLAlchemy 2.0, and
Pydantic v2 syntax via Context7 first; this file describes patterns by intent,
not by a pinned version.

## Layered, scalable structure

Start with the layered layout in `structure.md`. As the service grows, scale it
into a versioned, domain-modular shape rather than one flat folder:

```
app/
main.py create_app(): middleware, exception handlers, router mount
api/
v1/
router.py aggregates the v1 resource routers
<resource>.py thin route handlers per resource
deps.py shared dependencies (get_db, get_current_user, pagination)
core/
config.py Settings via pydantic-settings
security.py JWT, hashing
database.py async engine, sessionmaker, get_db
logging.py structured logging setup
domain/
<domain>/
models.py SQLAlchemy 2.0 Mapped models
schemas.py Pydantic v2 request and response models
service.py business logic, composes repositories
repository.py data access, the only place that runs queries
workers/ background jobs (optional: Celery, arq, or TaskiQ)
tests/
```

Version the public API from day one (`/api/v1`). Group by domain, not by
technical layer, once you pass a handful of resources. This is a modular monolith:
one deployable, clean internal seams, ready to split later.

## Do

- Keep route handlers thin: validate input with a schema, call a service, return
a response model. No business logic or SQL in the handler.
- One layer per job: handler -> service -> repository -> model. A layer only talks
to the one below it.
- Use async end to end: async engine, async session, `await` on all DB calls, and
`async def` handlers for anything that touches IO.
- Inject the DB session and the current user as dependencies (`Depends`). Never
build a session inside a handler.
- Define response models that exclude sensitive fields. Never return password
hashes, tokens, or internal ids you do not mean to expose.
- Validate and constrain input with Pydantic (types, bounds, enums). Reject bad
input at the edge.
- Paginate list endpoints. Never return an unbounded collection.
- Handle errors with typed exceptions and registered exception handlers that
return a clean, generic error body.
- Configure Alembic against the async engine URL from settings. Review every
autogenerated migration before applying.

## Do not

- Do not mix sync and async DB access. Pick async.
- Do not use the SQLAlchemy 1.x Query API (`session.query(...)`). Use `select()`
with an async session.
- Do not write Pydantic v1 patterns (`orm_mode`, `@validator`). Use
`from_attributes` and `@field_validator`.
- Do not put queries in services or handlers. Queries live in repositories.
- Do not leak `SECRET_KEY`, DB URLs, or provider keys into responses, logs, or
the OpenAPI schema.
- Do not catch broadly and swallow. Let typed exceptions bubble to the handlers.
- Do not trust client input for authorization. Re-check ownership on the server
for every mutating call.

## Nothing hardcoded

- All config comes from `pydantic-settings` reading env: DB URL, secret key,
token lifetimes, CORS origins, external URLs, feature flags. Provide
`.env.example` with every key.
- Fixed choices are Python enums, not string literals sprinkled across the code.
- Route path prefixes, pagination defaults, and limits are named constants, not
magic numbers inline.
- If you are about to type a literal secret, URL, or tunable number into a
handler or service, move it into settings or a constants module first.
68 changes: 68 additions & 0 deletions plugins/app-starter/skills/fastapi-app/references/quality-gates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# FastAPI quality gates, CI, and deploy

## Local gates (run before every commit and in CI)

```
ruff check .
ruff format --check .
pytest
python -c "from app.main import app" # imports cleanly
```

Run the app locally and confirm `/health` responds and `/docs` renders the
OpenAPI UI.

## CI workflow

`.github/workflows/ci.yml`, a job on PRs into `develop` and `main`:

```yaml
name: CI
on:
pull_request:
branches: [develop, main]
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready --health-interval 10s
--health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- run: pip install -r requirements.txt
- run: ruff check .
- run: pytest
env:
DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres
```

Pin the Python version to the one you install and the action majors to current.
Set branch protection to require the `build` job, then use
`gh pr merge <n> --squash --auto`.

## Docker

- `Dockerfile` on a slim Python base, install deps, run
`uvicorn app.main:app --host 0.0.0.0 --port 8000`.
- `docker-compose.yml` with the app plus Postgres for local dev.

## Vercel (serverless option)

- A `vercel.json` routing all paths to the ASGI app. Note serverless cold starts
and connection limits; use a pooled or serverless Postgres and keep the async
engine pool small.

## Verify before declaring done

`ruff check` clean, `pytest` green, the app imports and starts, `/health`
responds, and `alembic revision --autogenerate` produces a sane migration.
Report real results.
45 changes: 45 additions & 0 deletions plugins/app-starter/skills/fastapi-app/references/stack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# FastAPI stack

Default dependency set from the owner's shipped backend. Pin to the live stable
versions from `scripts/check-latest.sh`, not the numbers here.

## Core

- `fastapi` and `uvicorn[standard]` (ASGI server, standard extras for reload and
websockets).
- `pydantic` v2 and `pydantic-settings` for typed settings loaded from env.
- `python-multipart` for form and file uploads.

## Database (default: async Postgres)

- `sqlalchemy[asyncio]` on the 2.0 line, with `asyncpg` as the async driver.
- `alembic` for migrations.
- Use the 2.0 style: `async_engine`, `async_sessionmaker`, `Mapped[...]` typed
models, and `select()` statements. Do not write 1.x Query API or the old
`declarative_base` patterns from memory.

## Auth

- JWT via `python-jose[cryptography]` or `PyJWT`. Hash passwords with `bcrypt`
or `passlib[bcrypt]`.
- OAuth (Google) via `google-auth` when the app signs users in with Google.

## Optional

- `sqladmin` for a quick admin UI over the SQLAlchemy models.
- `httpx` for outbound HTTP (also the test client transport).

## Tooling

- `ruff` for lint and format (replaces flake8 + black + isort).
- `pytest` + `pytest-asyncio` for async tests.
- `uv` for dependency management and lockfile, or `pip` + `requirements.txt`.

## Version-boundary notes

- SQLAlchemy 2.0 is a hard break from 1.4. If you find yourself writing
`session.query(...)`, stop and use `select()` with an async session.
- Pydantic v2 changed validators, config, and serialization. `orm_mode` is now
`from_attributes`; `@validator` is now `@field_validator`. Confirm current
syntax in the Pydantic v2 docs.
- Do not mix sync and async DB sessions. Pick async end to end.
51 changes: 51 additions & 0 deletions plugins/app-starter/skills/fastapi-app/references/structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# FastAPI project structure

Layered structure, matching the owner's shipped backend.

```
<name>/
app/
main.py create_app(), router registration, middleware, /health
core/
config.py Settings (pydantic-settings), reads env
security.py JWT encode/decode, password hashing
database.py async engine, async_sessionmaker, get_db dependency
routers/ one module per resource, thin: parse, call service, return
services/ business logic, the only layer that composes repositories
models/ SQLAlchemy 2.0 Mapped models
schemas/ Pydantic v2 request and response models
utils/ helpers
alembic/ migration env and versions
alembic.ini
scripts/ seed and ops scripts
tests/
Dockerfile
docker-compose.yml # app + postgres for local dev
.env.example
requirements.txt # or pyproject.toml + uv.lock
```

## Patterns

- Routers are thin. They validate input via a `schemas/` model, call a service,
and return a response model. No business logic or raw SQL in routers.
- The DB session is a dependency:
```python
async def get_db() -> AsyncIterator[AsyncSession]:
async with async_session() as session:
yield session
```
Inject it with `db: AsyncSession = Depends(get_db)`.
- Settings come from `pydantic-settings` reading env. Never hardcode secrets or
URLs. Provide `.env.example` with every key.
- Auth: a `get_current_user` dependency decodes the JWT and loads the user.
Protected routes depend on it.
- Response models exclude sensitive fields. Never return password hashes or raw
tokens in a response schema.
- CORS, request logging, and error handlers are registered in `create_app()`.

## Migrations

- Configure Alembic to use the async engine URL from settings.
- Autogenerate after model changes: `alembic revision --autogenerate -m "..."`,
then review the generated migration before applying. Never edit the DB by hand.
Loading