Fastapi architect
Skill ralvarezdev/ralvaskills/skills/frameworks/fastapi-architect
My personal, ever-growing collection of AI skills for OpenCode and Claude Code. Enforces strict clean architecture and professional standards.
npx -y skills add ralvarezdev/ralvaskills --skill fastapi-architectAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Framework-specific delta on rest-api-architect — FastAPI 0.136 on Python 3.14. Feature layout, Pydantic v2 request/response separation, async DI with lifespan, URL-prefix versioning, RFC 7807 errors, in-house OAuth2+JWT or external IdP. Read rest-api-architect first for the cross-cutting REST conventions. Use when scaffolding or reviewing a FastAPI service.
SKILL.md
7.5 KB, as published. Nobody here has run it
FastAPI Architecture
Targets FastAPI 0.136 on Python 3.14. Companion to python-architect and sql-architect (data access via psycopg + .sql files). Implementation skeletons in RECIPES.md; pinned deps in STACK.md.
1. Project structure — feature-based
One folder per bounded context. Each feature owns its router, service, repo, schemas, and SQL files. Full tree in RECIPES.md.
router.pydepends onservice.py; never reaches intorepo.pydirectly.service.pyis pure Python — no FastAPI imports. Easy to unit-test.schemas.pyholds Pydantic models — never reused as ORM models or DB rows.
2. Routing & versioning
- URL-prefix versioning:
/v1/users,/v1/orders. Mount each version's routers under av1_router = APIRouter(prefix="/v1"). Deprecate by mounting/v2alongside, never by mutating/v1. - One
APIRouterper feature, included inmain.py. - Tags match feature folder names (
tags=["users"]) — drives OpenAPI grouping. - Path parameter types in the signature (
user_id: UUID) — FastAPI validates and parses for free. - Response model declared per route (
response_model=UserResponse) — sets the contract and trims extra fields automatically. - Status codes explicit (
status_code=status.HTTP_201_CREATED).
3. Pydantic schemas — separate request and response
Three shapes per resource: <Resource>Create (POST body), <Resource>Update (PATCH partial), <Resource>Response (response body). Example in RECIPES.md.
extra="forbid"on every request model. Unknown fields are an error, not silent acceptance.SecretStr/SecretBytesfor passwords, tokens. Stops accidental logging.Field(..., examples=[...])drives OpenAPI examples — clients get usable defaults.- Never reuse the same model for request and response. Read-only fields leak into PATCH payloads otherwise.
- Pydantic v2 validators:
@field_validatorfor per-field,@model_validator(mode="after")for cross-field invariants.
4. Dependency injection
- Single source of shared state via
Depends. DB connections, HTTP clients, auth subjects — all injected, never imported as module-level globals. - Async dependencies for anything I/O-bound:
async def get_db() -> AsyncIterator[AsyncConnection]: .... - Sub-dependencies for layered composition:
get_current_userdepends ondecode_tokendepends onget_settings. FastAPI resolves the graph and caches per-request. - Use type aliases to keep route signatures clean (see RECIPES.md).
5. Lifespan & startup
Lifespan context is the only place to open/close shared resources (DB pool, HTTP client, cache, message bus). Never in module-level code or @app.on_event (deprecated). Settings loaded at startup, validated once via pydantic-settings. Skeleton in RECIPES.md.
6. Authentication & authorization
Patterns (in-house JWT vs external IdP, Argon2id, JWT lifetimes, JWKS verification, switching criterion) live in rest-api-architect/AUTH_PATTERNS.md. FastAPI-specific implementation:
- Pattern A — in-house OAuth2 + JWT uses FastAPI's
OAuth2PasswordBearer+pyjwt+argon2-cffi. Dependency skeleton in RECIPES.md. - Pattern B — external IdP uses
pyjwt'sPyJWKClientfor JWKS verification; cache via@lru_cache. Verifyaudandissexplicitly. - Authorization is route-level via dependencies, not middleware —
dependencies=[Depends(require_scope("users:delete"))]on the route. Skeleton in RECIPES.md.
7. Error handling — RFC 7807 Problem Details
Every error returns application/problem+json with a standardised shape (per rest-api-architect §7). Handler skeleton in RECIPES.md.
- One handler per domain-exception family. Never let
HTTPExceptionand your custom exceptions return different shapes. - Validation errors (
RequestValidationError) get their own handler that maps Pydantic's error list intoProblem.detail. - Never leak stack traces in
detail. Log them server-side with a correlation id; reference the id in the response.
8. Middleware
Order matters — outermost middleware sees the request first.
- CORS (
CORSMiddleware) — first, so preflights short-circuit before auth. - Compression (
GZipMiddleware, min_size=1000). - Request ID (custom) — generate a UUID per request, attach to logs and response header.
- Logging (custom) — structured logs with method, path, status, latency, request id.
- Auth is a dependency, not middleware — per-route, lets unauthenticated endpoints (login, health) coexist cleanly.
9. Background tasks
BackgroundTasksfor genuinely fire-and-forget work that's tied to one response (sending a confirmation email, writing a metric). The task runs after the response is sent but in the same process — failures are invisible to the client.- Anything serious (retryable, distributed, scheduled) belongs in a real task queue — flag for a future
task-queue-architectskill.BackgroundTasksis not a queue.
10. Testing
TestClientfor end-to-end synchronous tests against the ASGI app.httpx.AsyncClientwithASGITransportfor async tests that need to exercise async dependencies fully.- Override dependencies in tests via
app.dependency_overrides[get_db] = .... Reset after the test. - DB fixtures: run migrations into a per-test schema, or wrap each test in a rolled-back transaction (faster).
- Snapshot the OpenAPI spec in CI:
assert app.openapi() == json.load(open("tests/openapi.snapshot.json")). Catches accidental contract changes.
11. OpenAPI & docs
- Tags, summaries, descriptions on every route. They drive the rendered docs and SDK code generation.
responses={...}to document non-default status codes with their shapes (401,403,404,422).include_in_schema=Falseon internal endpoints (health, metrics, debug).- Customise the spec in
app.openapi()to addinfo.contact,servers,securitySchemes— these aren't FastAPI defaults.
12. Performance
- All routes are
async defunless they call a synchronous library and you've decided not to wrap it. - Never
time.sleep,requests, or other blocking calls insideasync def. Block-detection:asyncio.get_event_loop().slow_callback_duration = 0.1in dev. - Run sync I/O in a thread:
await asyncio.to_thread(blocking_fn, args). - Connection pooling: open the DB pool once in
lifespan(see §5); neverpsycopg.connect()per request. response_model_exclude_unset=Truewhen returning a large model with many optional fields — avoids serialising defaults.- Pagination at the API layer mirrors the SQL pattern (see sql-architect §4): cursor over offset.