FastAPI Routes
context7: If the
mcp_context7tool is available, resolve and load the fullfastapidocumentation before proceeding:mcp_context7_resolve-library-id: "fastapi" mcp_context7_get-library-docs: <resolved-id>
Guidelines and patterns for writing FastAPI routes in the Oscilla codebase.
REST Principles
APIs must adhere as closely as possible to REST principles, including appropriate use of HTTP verbs:
| Verb | Usage |
|---|---|
GET |
Read one or many resources |
POST |
Create a new resource |
PUT |
Replace / fully update a resource |
PATCH |
Partial update (use sparingly) |
DELETE |
Remove a resource |
Pydantic Models
- Always use Pydantic models for both input and output — never use plain
dictorAny. - Never reuse the same model for input and output. Use separate models:
PostCreate— user input for creating a resourcePostUpdate— user input for updating (optional fields)PostRead— response shape returned to the client
- Parameters in input models must use
Field()with validation constraints and adescription. - Output models do not require
Field()unless you need aliases or serialization control.
from uuid import UUID
from pydantic import BaseModel, Field
class PostCreate(BaseModel):
title: str = Field(min_length=1, max_length=200, description="Post title")
content: str = Field(min_length=1, description="Post content")
class PostRead(BaseModel):
id: UUID
title: str
content: str
created_at: str
class PostUpdate(BaseModel):
title: str | None = Field(default=None, max_length=200)
content: str | None = None
Router Pattern
Use APIRouter for all routes. Register the router in oscilla/www.py under the /api prefix.
from uuid import UUID
from fastapi import APIRouter, status
router = APIRouter()
@router.post("/posts", response_model=PostRead, status_code=status.HTTP_201_CREATED)
async def create_post(post: PostCreate) -> PostRead:
...
@router.get("/posts/{post_id}", response_model=PostRead)
async def get_post(post_id: UUID) -> PostRead:
...
@router.put("/posts/{post_id}", response_model=PostRead)
async def update_post(post_id: UUID, post: PostUpdate) -> PostRead:
...
@router.delete("/posts/{post_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_post(post_id: UUID) -> None:
...
Route Registration
All application routes must be registered under the /api prefix in oscilla/www.py.
Exceptions (no /api prefix by convention):
/health— liveness probe/ready— readiness probe/static— static file serving
# oscilla/www.py
app.include_router(posts_router, prefix="/api")
OpenAPI Docs
The OpenAPI documentation is served at these fixed paths — do not move them:
| URL | Interface |
|---|---|
/api/docs |
Swagger UI |
/api/redoc |
ReDoc |
/api/openapi.json |
Raw schema |
Error Handling
Use HTTPException with appropriate status codes:
from fastapi import HTTPException, status
@router.get("/posts/{post_id}", response_model=PostRead)
async def get_post(post_id: UUID) -> PostRead:
post = await get_post_from_db(post_id)
if post is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Post not found")
return post
Style Checklist
Before submitting a new route, verify:
- HTTP verb matches the operation semantics
- Separate
Create,Update, andReadmodels defined - All input model fields use
Field()with description - Route is
async - Router is registered under
/apiinoscilla/www.py - 201 status used for
POSTcreate endpoints - 204 status used for
DELETEendpoints (no response body) - Path parameters use typed UUIDs where appropriate
Further Reading
- docs/dev/api.md — Full FastAPI developer guide covering the application structure, startup events, Swagger UI (
/api/docs), ReDoc (/api/redoc), OpenAPI schema, middleware, authentication integration, and dependency injection patterns. - docs/dev/authentication.md — Authentication and authorization patterns used in this project.
- docs/dev/dependencies.md — FastAPI dependency injection conventions.
- FastAPI Docs
Source: tedivm/oscilla — distributed by TomeVault.