Streaming Responses and Server-Sent Events in FastAPI
Some responses shouldn't wait until everything is ready: large exports, progress updates, or text from an LLM. FastAPI's StreamingResponse sends data as your generator produces it.
Streaming a large CSV
from fastapi.responses import StreamingResponse
@app.get("/export/orders.csv")
async def export_orders(db: DbSession):
async def rows():
yield "id,total,created_at\n"
result = await db.stream(select(Order.id, Order.total, Order.created_at))
async for r in result:
yield f"{r.id},{r.total},{r.created_at.isoformat()}\n"
return StreamingResponse(rows(), media_type="text/csv",
headers={"Content-Disposition": "attachment; filename=orders.csv"})
Server-Sent Events
import asyncio, json
@app.get("/jobs/{job_id}/events")
async def job_events(job_id: str, request: Request):
async def events():
while True:
if await request.is_disconnected():
break
status = await jobs.status(job_id)
yield f"data: {json.dumps(status)}\n\n"
if status["state"] in ("done", "failed"):
break
await asyncio.sleep(1)
return StreamingResponse(events(), media_type="text/event-stream",
headers={"Cache-Control": "no-cache"})
Streaming LLM output
import anthropic
client = anthropic.AsyncAnthropic()
@app.post("/chat")
async def chat(body: ChatRequest):
async def tokens():
async with client.messages.stream(
model="claude-opus-5-5",
max_tokens=64000,
messages=body.messages,
) as stream:
async for text in stream.text_stream:
yield f"data: {json.dumps({'delta': text})}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(tokens(), media_type="text/event-stream")
Tips
- Use async generators with async libraries so streaming doesn't block the event loop.
- Disable buffering in proxies (for example
X-Accel-Buffering: nofor Nginx). - Check
request.is_disconnected()to stop work when the client leaves.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.