14 Prompts for FastAPI: Endpoints, Pydantic, and Background Tasks

FastAPI has taken the Python web world by storm. Its speed, automatic OpenAPI docs, and type-checked request handling make it the go-to framework for modern APIs. But writing high-quality FastAPI code still requires attention to detail and familiarity with patterns like dependency injection, lifecycle management, and async I/O.

AI assistants can accelerate your work, but only if you know how to ask the right questions. This collection of 14 prompts covers the most common FastAPI scenarios. Each prompt is followed by a worked example so you can see the expected output. Use them as a starting point and adapt them to your specific needs.

1. Getting Started: Project Skeleton

1. Minimal App with Health Check

Task: Create a minimal FastAPI application with a health endpoint.

Prompt: "Generate a FastAPI app with a GET /health endpoint that returns a JSON object with the app version. Use the lifespan context to print startup and shutdown messages."

Example result:

from fastapi import FastAPI
from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    print("Starting up...")
    yield
    print("Shutting down...")

app = FastAPI(lifespan=lifespan)
VERSION = "1.0.0"

@app.get("/health")
async def health():
    return {"version": VERSION, "status": "ok"}

The lifespan parameter (introduced in recent FastAPI versions) is the recommended way to handle startup and shutdown events. The health endpoint helps DevOps probes check if the service is alive.

2. Pydantic User Registration Model

Task: Define a Pydantic model for user registration with validation.

Prompt: "Write a Pydantic model UserCreate with email, password, and age. email must be a valid email, password at least 8 characters, age optional and between 18 and 99. Use EmailStr from pydantic[email]."

Example result:

from pydantic import BaseModel, EmailStr, Field

class UserCreate(BaseModel):
    email: EmailStr
    password: str = Field(..., min_length=8)
    age: int | None = Field(None, ge=18, le=99)

    @field_validator("password")
    @classmethod
    def validate_password(cls, v):
        if " " in v:
            raise ValueError("Password cannot contain spaces")
        return v

This model ensures that invalid data is rejected before it reaches your endpoint. Pydantic's Field provides both constraints and documentation, and the field_validator adds custom logic, as shown in the official Pydantic docs.

2. RESTful Endpoints

3. CRUD for a To-Do API

Task: Create CRUD endpoints for a to-do item stored in memory.

Prompt: "Build a FastAPI router for to-do items with endpoints to create, read, update, and delete. Use a list as storage and return status codes 201, 404, and 204."

Example result:

from fastapi import APIRouter, HTTPException, status

router = APIRouter(prefix="/todos")
todos = []
next_id = 1

@router.post("/", status_code=status.HTTP_201_CREATED)
def create_todo(item: TodoCreate):
    global next_id
    todo = {"id": next_id, **item.dict()}
    todos.append(todo)
    next_id += 1
    return todo

@router.get("/{todo_id}")
def get_todo(todo_id: int):
    for todo in todos:
        if todo["id"] == todo_id:
            return todo
    raise HTTPException(status_code=404, detail="Todo not found")

@router.put("/{todo_id}")
def update_todo(todo_id: int, item: TodoUpdate):
    # ... standard logic
    pass

@router.delete("/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_todo(todo_id: int):
    # ... standard logic, return None
    pass

You'll also need to define TodoCreate and TodoUpdate Pydantic models. This example is simple, but in production you'd replace the list with a database, as described in the FastAPI SQLAlchemy tutorial.

4. Query Parameters with Validation

Task: Write an endpoint that uses query parameters with default values and validation.

Prompt: "Create a /search endpoint with query parameters q (required), page (default 1), and size (default 10, maximum 100). Use Annotated for documentation."

Example result:

from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/search")
async def search(
    q: str,
    page: int = Query(1, ge=1),
    size: int = Query(10, ge=1, le=100)
):
    return {"query": q, "page": page, "size": size}

Using Query helps the OpenAPI schema include constraints like ge and le. This is one of the simplest ways to get free validation and documentation, a feature highlighted in the FastAPI docs.

5. Dependency Injection for Database Session

Task: Create a dependency that provides a database session to endpoints.

Prompt: "Use FastAPI's Depends to create a database session dependency. Assume SQLAlchemy. get_db should yield a session and close it afterwards."

Example result:

from fastapi import Depends
from sqlalchemy.orm import Session
from .database import SessionLocal

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/users/{user_id}")
def read_user(user_id: int, db: Session = Depends(get_db)):
    return db.query(User).filter(User.id == user_id).first()

Dependencies are FastAPI's killer feature. They make testing easy and encourage code reuse. The same pattern is used for authentication, database sessions, and per-request objects. See the FastAPI dependencies docs for details.

3. Async and Background Processing

6. Background Email After Signup

Task: Send a welcome email in the background after user creation.

Prompt: "Add a background task to a user registration endpoint. The task should print and then send an email. Use BackgroundTasks from FastAPI."

Example result:

from fastapi import BackgroundTasks

def send_welcome_email(email: str):
    print(f"Sending email to {email}")
    # actual email sending code

@app.post("/signup")
async def signup(user: UserCreate, background_tasks: BackgroundTasks):
    # save user to database
    background_tasks.add_task(send_welcome_email, user.email)
    return {"message": "User created"}

FastAPI's BackgroundTasks is ideal for operations that don't need to block the response. It runs after the response is sent, which improves perceived latency. The official background tasks guide has more examples.

7. File Cleanup with BackgroundTasks

Task: Upload a file and remove it later in the background.

Prompt: "Create an endpoint that accepts a file upload, saves it to disk, and schedules a background task to delete it after 5 minutes. Use BackgroundTasks and time.sleep."

Example result:

import os
import time
from fastapi import BackgroundTasks, UploadFile, File

def delete_after_delay(path: str, delay: int):
    time.sleep(delay)
    if os.path.exists(path):
        os.remove(path)

@app.post("/upload")
async def upload(file: UploadFile = File(...), background_tasks: BackgroundTasks):
    path = f"temp/{file.filename}"
    with open(path, "wb") as f:
        f.write(await file.read())
    background_tasks.add_task(delete_after_delay, path, 300)
    return {"filename": file.filename}

Note: time.sleep blocks the event loop if called in an async endpoint. For production, use asyncio.sleep or better, use a task queue like Celery for such delayed jobs. This example is just for demonstration.

8. Concurrent External API Calls

Task: Fetch data from multiple external APIs concurrently.

Prompt: "Write an async endpoint that calls two external APIs in parallel using httpx.AsyncClient. Return a combined response. Use asyncio.gather."

Example result:

import httpx
import asyncio
from fastapi import FastAPI

app = FastAPI()

@app.get("/combined")
async def combined():
    async with httpx.AsyncClient() as client:
        resp1, resp2 = await asyncio.gather(
            client.get("https://api1.example.com/data"),
            client.get("https://api2.example.com/data")
        )
    return {"api1": resp1.json(), "api2": resp2.json()}

httpx is FastAPI's official recommended HTTP client for async operations. Using gather ensures that the requests run concurrently, drastically reducing total response time. You can read more in the httpx docs.

4. Pydantic Validation and Serialization

9. Pagination with Pydantic Models

Task: Implement a paginated response model.

Prompt: "Create a generic pagination response schema using Pydantic. It should have items (list), total, page, size. Then use it in a /books endpoint."

Example result:

from pydantic import BaseModel
from typing import Generic, TypeVar, List

T = TypeVar("T")

class PaginatedResponse(BaseModel, Generic[T]):
    items: List[T]
    total: int
    page: int
    size: int

@app.get("/books", response_model=PaginatedResponse[Book])
async def get_books(page: int = 1, size: int = 10):
    books = book_db.fetch_paginated(page, size)
    return {
        "items": books,
        "total": book_db.count(),
        "page": page,
        "size": size
    }

Generic Pydantic models are powerful for creating reusable response wrappers. FastAPI integrates this natively, as stated in the response model documentation.

10. Hiding Sensitive Fields

Task: Return a user object without exposing the password hash.

Prompt: "Define a UserResponse model and a UserInDB model. The response model should exclude password_hash. Use response_model in the endpoint."

Example result:

class UserInDB(BaseModel):
    id: int
    username: str
    password_hash: str

class UserResponse(BaseModel):
    id: int
    username: str

@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    return user

FastAPI will filter out fields that are not in the response model. This is a security best practice. You can also use exclude in the model config, but defining a dedicated response model is clearer. See the official tutorial.

11. Advanced Validation with Pydantic

Task: Validate fields based on other fields' values.

Prompt: "Create a CreateReservation model where end_time must be after start_time. Use a model_validator."

Example result:

from pydantic import BaseModel, model_validator
from datetime import datetime

class CreateReservation(BaseModel):
    start_time: datetime
    end_time: datetime

    @model_validator(mode="after")
    def check_times(self):
        if self.end_time <= self.start_time:
            raise ValueError("end_time must be after start_time")
        return self

Pydantic v2's model_validator allows cross-field validation. This pattern is used in many scheduling applications. The official Pydantic validators guide explains the difference between mode="before" and mode="after".

5. Advanced Patterns

12. Rate Limiting Middleware

Task: Implement a simple rate limiter as middleware.

Prompt: "Write a FastAPI middleware that limits each client IP to 10 requests per minute. Use token bucket in memory. Return 429 if exceeded."

Example result:

import time
from fastapi import Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware

class RateLimitMiddleware(BaseHTTPMiddleware):
    def __init__(self, app, limit=10, window=60):
        super().__init__(app)
        self.limit = limit
        self.window = window
        self.requests = {}

    async def dispatch(self, request: Request, call_next):
        client_ip = request.client.host
        now = time.time()
        bucket = self.requests.get(client_ip, [])
        bucket = [timestamp for timestamp in bucket if now - timestamp < self.window]
        if len(bucket) >= self.limit:
            return JSONResponse(status_code=429, content={"detail": "Too many requests"})
        bucket.append(now)
        self.requests[client_ip] = bucket
        return await call_next(request)

app.add_middleware(RateLimitMiddleware)

This is a minimal in-memory implementation. For distributed systems, you'd use Redis or a dedicated library like slowapi. The middleware pattern is described in the Starlette docs.

13. Lifespan Events for Resource Management

Task: Use safe lifespan events to initialize an application-level resource.

Prompt: "Use lifespan to create an httpx.AsyncClient as app.state.client and close it during shutdown."

Example result:

from contextlib import asynccontextmanager
import httpx
from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.client = httpx.AsyncClient()
    yield
    await app.state.client.aclose()

app = FastAPI(lifespan=lifespan)

@app.get("/external")
async def external():
    async with app.state.client as client:
        resp = await client.get("https://example.com")
    return resp.json()

The lifespan is the modern replacement for the deprecated @app.on_event decorator. It's the recommended way to manage resources as shown in the FastAPI advanced docs.

14. WebSocket Endpoint for Notifications

Task: Create a WebSocket endpoint that echoes messages back to the client.

Prompt: "Implement a WebSocket endpoint in FastAPI that accepts a connection and sends a welcome message, then echoes any received message. Add a simple JSON message schema."

Example result:

from fastapi import WebSocket, WebSocketDisconnect

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
    await websocket.accept()
    await websocket.send_text(f"Welcome client {client_id}")
    try:
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"Echo: {data}")
    except WebSocketDisconnect:
        print(f"Client {client_id} disconnected")

WebSockets are useful for real-time features like chat and live dashboards. FastAPI supports them natively, and the docs provide a full example.

Wrapping Up

These 14 prompts represent the bread and butter of FastAPI development. They show how a well-structured prompt can teach you production patterns without hours of reading documentation. The key takeaway is to always include constraints, expected behavior, and a context in your prompt. That way an AI assistant can generate code that fits your exact use case.

If you have a favorite FastAPI prompt that isn't on this list, share it in the comments below. And if you want to go deeper, the official FastAPI documentation is an excellent resource that I've linked throughout this article.

← All posts

Comments