$npx -y skills add mjunaidca/mjs-agent-skills --skill fastapi-backendBuild production-grade FastAPI backends with SQLModel, Pydantic, and JWT authentication. Use this skill when building REST APIs, integrating with Neon PostgreSQL, implementing Better Auth JWT verification, or creating CRUD endpoints. Includes patterns for audit logging, worker/ag
| 1 | # FastAPI Backend |
| 2 | |
| 3 | Build production-grade FastAPI backends with SQLModel, Pydantic v2, and JWT/JWKS authentication patterns. |
| 4 | |
| 5 | ## When to Use |
| 6 | |
| 7 | - Building REST API endpoints with FastAPI |
| 8 | - Creating SQLModel schemas for Neon PostgreSQL |
| 9 | - Implementing JWT verification against Better Auth JWKS |
| 10 | - Designing OpenAPI contracts for frontend consumption |
| 11 | - Adding audit logging to API operations |
| 12 | - Ensuring human-agent parity in API design |
| 13 | |
| 14 | ## Quick Start |
| 15 | |
| 16 | ```bash |
| 17 | # Project setup |
| 18 | uv init backend && cd backend |
| 19 | uv add fastapi sqlmodel pydantic httpx python-jose uvicorn |
| 20 | |
| 21 | # Development |
| 22 | uv run uvicorn main:app --reload --port 8000 |
| 23 | |
| 24 | # Access docs |
| 25 | open http://localhost:8000/docs # Swagger UI with Authorize button |
| 26 | ``` |
| 27 | |
| 28 | ## Core Patterns |
| 29 | |
| 30 | ### 1. SQLModel Schema (Database + API) |
| 31 | |
| 32 | SQLModel combines SQLAlchemy and Pydantic. Use `table=True` for database models: |
| 33 | |
| 34 | ```python |
| 35 | from sqlmodel import SQLModel, Field |
| 36 | from datetime import datetime |
| 37 | from typing import Optional, Literal |
| 38 | |
| 39 | # Base model (shared fields, no table) |
| 40 | class TaskBase(SQLModel): |
| 41 | title: str = Field(max_length=200) |
| 42 | description: Optional[str] = None |
| 43 | status: Literal["pending", "in_progress", "review", "completed", "blocked"] = "pending" |
| 44 | priority: Literal["low", "medium", "high", "critical"] = "medium" |
| 45 | progress_percent: int = Field(default=0, ge=0, le=100) |
| 46 | assigned_to: Optional[str] = None |
| 47 | project_slug: Optional[str] = None |
| 48 | parent_id: Optional[int] = None |
| 49 | |
| 50 | # Database model (has table) |
| 51 | class Task(TaskBase, table=True): |
| 52 | id: Optional[int] = Field(default=None, primary_key=True) |
| 53 | created_at: datetime = Field(default_factory=datetime.now) |
| 54 | updated_at: datetime = Field(default_factory=datetime.now) |
| 55 | |
| 56 | # API models (no table, for request/response) |
| 57 | class TaskCreate(TaskBase): |
| 58 | pass |
| 59 | |
| 60 | class TaskUpdate(SQLModel): |
| 61 | title: Optional[str] = None |
| 62 | description: Optional[str] = None |
| 63 | status: Optional[str] = None |
| 64 | priority: Optional[str] = None |
| 65 | progress_percent: Optional[int] = None |
| 66 | assigned_to: Optional[str] = None |
| 67 | |
| 68 | class TaskRead(TaskBase): |
| 69 | id: int |
| 70 | created_at: datetime |
| 71 | updated_at: datetime |
| 72 | ``` |
| 73 | |
| 74 | ### 2. Neon PostgreSQL Connection |
| 75 | |
| 76 | ```python |
| 77 | from sqlmodel import create_engine, Session |
| 78 | import os |
| 79 | |
| 80 | # Neon connection string |
| 81 | DATABASE_URL = os.getenv("DATABASE_URL") # postgresql://user:pass@host/db?sslmode=require |
| 82 | |
| 83 | engine = create_engine(DATABASE_URL, echo=True) |
| 84 | |
| 85 | def get_session(): |
| 86 | with Session(engine) as session: |
| 87 | yield session |
| 88 | ``` |
| 89 | |
| 90 | ### 3. CRUD Endpoints |
| 91 | |
| 92 | ```python |
| 93 | from fastapi import FastAPI, Depends, HTTPException, Query |
| 94 | from sqlmodel import Session, select |
| 95 | |
| 96 | app = FastAPI(title="TaskFlow API", version="1.0.0") |
| 97 | |
| 98 | @app.post("/api/tasks", response_model=TaskRead, status_code=201) |
| 99 | def create_task( |
| 100 | task: TaskCreate, |
| 101 | session: Session = Depends(get_session), |
| 102 | current_user: User = Depends(get_current_user), |
| 103 | ): |
| 104 | db_task = Task.model_validate(task) |
| 105 | session.add(db_task) |
| 106 | session.commit() |
| 107 | session.refresh(db_task) |
| 108 | |
| 109 | # Audit log |
| 110 | log_action(session, "created", current_user.id, task_id=db_task.id) |
| 111 | |
| 112 | return db_task |
| 113 | |
| 114 | @app.get("/api/tasks", response_model=list[TaskRead]) |
| 115 | def list_tasks( |
| 116 | session: Session = Depends(get_session), |
| 117 | current_user: User = Depends(get_current_user), |
| 118 | status: Optional[str] = Query(None), |
| 119 | assigned_to: Optional[str] = Query(None), |
| 120 | project: Optional[str] = Query(None), |
| 121 | limit: int = Query(50, le=100), |
| 122 | offset: int = Query(0, ge=0), |
| 123 | ): |
| 124 | query = select(Task) |
| 125 | |
| 126 | if status: |
| 127 | query = query.where(Task.status == status) |
| 128 | if assigned_to: |
| 129 | query = query.where(Task.assigned_to == assigned_to) |
| 130 | if project: |
| 131 | query = query.where(Task.project_slug == project) |
| 132 | |
| 133 | query = query.offset(offset).limit(limit) |
| 134 | return session.exec(query).all() |
| 135 | |
| 136 | @app.get("/api/tasks/{task_id}", response_model=TaskRead) |
| 137 | def get_task( |
| 138 | task_id: int, |
| 139 | session: Session = Depends(get_session), |
| 140 | current_user: User = Depends(get_current_user), |
| 141 | ): |
| 142 | task = session.get(Task, task_id) |
| 143 | if not task: |
| 144 | raise HTTPException(status_code=404, detail="Task not found") |
| 145 | return task |
| 146 | |
| 147 | @app.patch("/api/tasks/{task_id}", response_model=TaskRead) |
| 148 | def update_task( |
| 149 | task_id: int, |
| 150 | task_update: TaskUpdate, |
| 151 | session: Session = Depends(get_session), |
| 152 | current_user: User = Depends(get_current_user), |
| 153 | ): |
| 154 | task = session.get(Task, task_id) |
| 155 | if not task: |
| 156 | raise HTTPException(status_code=404, detail="Task not found") |
| 157 | |
| 158 | update_data = task_update.model_dump(exclude_unset=True) |
| 159 | for key, value in update_data.items(): |
| 160 | setattr(task, key, value) |
| 161 | |
| 162 | task.updated_at = datetime.now |