12 Cursor Rules to Stop Your AI Assistant from Writing Outdated FastAPI Code

Constrain AI with Cursor project rules to eliminate outdated FastAPI patterns.
The article tackles AI assistants generating outdated FastAPI code by proposing scoped, glob-loaded Cursor `.mdc` rules. Twelve conventions span four areas: routes/schemas (Pydantic v2, thin async routes, explicit response_model, XCreate/XUpdate/XRead naming), database (SQLAlchemy 2.0 style, service-owned transactions, lazy="raise" with explicit loading, naming conventions), errors/config (custom AppError, cached get_settings, no f-string SQL), and testing (real Postgres with httpx.AsyncClient and ASGITransport, per-test rollback). Rules can be hand-written or bought as a pack.
AI coding assistants often produce FastAPI patterns that were popular years ago: .dict(), orm_mode, session.query(), throwing HTTPException deep in the service layer, and using SQLite for tests. The code runs, but it's not the code you should be writing today. Cursor's project rules solve most of this—each rule is a .mdc file under .cursor/rules/, loaded via glob only when editing matching files. The author summarizes the 12 most effective conventions.

Routing & Schemas: Enforce Pydantic v2 and Thin Routes
1. Use only the Pydantic v2 API. Use model_validate, model_dump, ConfigDict, field_validator; never .dict(), parse_obj, orm_mode, class Config, or the v1 validator decorator. This rule directly cuts off the nostalgia errors AI makes most often.
2. Keep routes thin and async. Parse the request, call the service, return the schema. No SQL or business rules in routes. This ensures separation of concerns and makes the service layer the single entry point for business logic.
3. Always declare response_model (or a return type annotation) and an explicit status_code: 201 for create, 204 for delete. Explicit declarations make the API contract clear, and AI won't arbitrarily return the default 200.
4. Name schemas XCreate/XUpdate/XRead. Use ConfigDict(extra="forbid") on input models so a sneakily passed "is_admin": true gets rejected. For updates, apply partial updates with model_dump(exclude_unset=True). The naming convention keeps AI-generated structures consistent.
The Pydantic v1 to v2 migration is the biggest breaking change in the FastAPI ecosystem in recent years. v2 rewrote the core validation logic in Rust, boosting performance 5–50x, while merging orm_mode into ConfigDict(from_attributes=True), renaming parse_obj/dict() to model_validate/model_dump, and splitting validator into field_validator and model_validator. Since the vast majority of public tutorials and Q&A still date from the v1 era, AI models easily mix the two APIs during completion, causing AttributeError or silent type-conversion differences that only surface at runtime. Locking the API surface to v2 via rules essentially removes "version drift"—a low-value problem—from code review entirely.
Database: Lock In SQLAlchemy 2.0 Style and Explicit Loading
5. Use only SQLAlchemy 2.0 style. Use Mapped[...], mapped_column(), select(); never session.query() or declarative_base(). AI training data is full of legacy patterns, and this rule enforces the modern async style.
6. The service layer owns transactions, using async with session.begin():. The repository layer never commits. Use async_sessionmaker(expire_on_commit=False). Clear transaction boundaries avoid async I/O issues caused by implicit commits.
7. Relationships default to lazy="raise". Load explicitly with selectinload() or joinedload() so there's no hidden I/O in async code. Lazy loading is a time bomb in async environments, and this rule defuses it upfront.
8. Set MetaData(naming_convention=...) on Base so Alembic constraint names are deterministic. Never edit applied migrations. Naming conventions make database migrations predictable, and AI won't generate random constraint names.
SQLAlchemy's lazy="raise" is a defensive loading strategy: any relationship not explicitly preloaded throws an exception immediately when accessed, rather than silently issuing a query. In synchronous code, lazy loading only causes N+1 query performance issues; but in an asyncio environment, attribute access happens outside the I/O loop, triggering MissingGreenlet errors that often surface only during response serialization—making them costly to debug. Setting lazy="raise" as the default, combined with selectinload() (issuing a single IN query to batch-load) or joinedload() (fetching in one JOIN), forces hidden I/O into the open during development, making "which relationships are loaded" an explicit contract rather than a runtime surprise.
Errors, Config & Security: Unified Error Handling and a Single Config Entry Point
9. The service layer throws its own AppError subclasses, never HTTPException. A single exception handler maps them to {"error": {"code": ..., "message": ...}}. This keeps the service layer decoupled from HTTP and standardizes the error format.
10. Settings only via a cached get_settings(), based on pydantic-settings. Never read os.environ elsewhere; use SecretStr for secrets. Centralized config management avoids scattered environment variable reads.
11. Never build SQL with f-strings. Use bound parameters or text(...).bindparams(). This security rule prevents SQL injection, which AI sometimes uses f-strings for out of convenience.
Testing: Real Postgres and Async Client
12. pytest + pytest-asyncio + httpx.AsyncClient(transport=ASGITransport(app=app)), against real Postgres, with each test rolled back. Clean up app.dependency_overrides after each test. Don't substitute SQLite. Real-database tests catch more issues, and rollbacks maintain isolation.
ASGITransport is a transport-layer adapter provided by httpx that hands requests directly to the ASGI application object without going through a real network port. Tests still exercise the full middleware, dependency injection, and exception handling chain, but skip the overhead of starting uvicorn and managing ports, while keeping the familiar AsyncClient interface. The insistence on real Postgres rather than SQLite is because the two differ significantly in JSONB, array types, ON CONFLICT, transaction isolation levels, and constraint behavior—code that passes tests with SQLite may fail outright in production. Running each test inside a transaction and rolling back keeps tests from polluting each other while avoiding the expensive overhead of repeatedly creating and dropping databases.
How to Use These Rules
Place these rules in .cursor/rules/, one file per domain, with globs matching your layout. For Claude Code, the same conventions can serve as a short CLAUDE.md at the repository root. The author also offers a FastAPI rules pack containing 7 scoped .mdc files and a CLAUDE.md for $12, with two files available as free downloads (no email required). All conventions can be implemented manually without buying anything.
The core idea behind these rules: don't rely on AI's default training data; actively constrain generation behavior with project rules. Each rule targets a specific outdated pattern, simple and direct. You can start with the most frequent errors and gradually add your own rules.
One-line Summary
Constrain AI with Cursor project rules to eliminate outdated FastAPI patterns.
Paragraph Summary
The article addresses AI coding assistants' tendency to generate outdated FastAPI code by proposing scoped, glob-loaded Cursor .mdc project rules to actively constrain generation. The author summarizes 12 conventions across four layers: routing and schema layer enforcing Pydantic v2 APIs, thin async routes, explicit response_model and status codes, XCreate/XUpdate/XRead naming with extra="forbid"; database layer locking in SQLAlchemy 2.0's Mapped/select() style, service-layer-owned transactions, lazy="raise" relationships with explicit preloading, and naming conventions on Base; error and config layer requiring custom AppError instead of HTTPException, unified config via cached get_settings(), and no f-string SQL; and testing layer using real Postgres with httpx.AsyncClient and ASGITransport, rolling back each test. Rules can be implemented manually or purchased as a ready-made pack.
Related articles

Chain of Thought Isn't Just for AI: How Humans Can Use CoT to Improve Thinking Quality
Chain of Thought isn't just a prompt trick for large models. Starting from a Hacker News discussion, this article analyzes why CoT works for human thinking too, breaks down actionable ways to transfer it, and highlights the key differences between human CoT and machine CoT.
Cloudflare Teams Up with OpenAI: Closi…
Cloudflare Teams Up with OpenAI: Closing the Loop from Vulnerability Discovery to Defense with AI
Cloudflare uses OpenAI Daybreak to investigate security vulnerabilities and generate reviewable remediation plans, while developers use Codex to boost coding efficiency and quality.

Orca Multi-Agent Orchestration in Practice: A Guide to Parallel Collaboration with Claude and Codex
Orca is Stable AI's free open-source agent workbench supporting 34 agents like Claude Code and Codex in parallel. Learn its Git worktree isolation, multi-model orchestration, and mobile management.