FastAPI provides structured error responses. Use HTTPException for expected errors:
Copy
from fastapi import HTTPException@app.post("/api/users", status_code=201)def create_user(data: UserCreate): # Business logic error if any(u["email"] == data.email for u in users): raise HTTPException( status_code=409, detail="A user with this email already exists" ) new_user = save_user(data) return new_user@app.get("/api/users/{user_id}")def get_user(user_id: int): user = find_user(user_id) if not user: raise HTTPException( status_code=404, detail="User not found" ) return user
Pydantic handles validation errors automatically. If someone sends { "name": 123 } instead of a string, FastAPI returns a 422 with details — you don’t need to code this.
Write clear detail messages. They travel all the way to the user’s screen. “A user with this email already exists” is better than “Conflict” or “Error 409”.
Every component that makes API calls should handle:
Network failures — “Unable to connect” message with retry option
Validation errors — field-level messages next to inputs
Not found (404) — “Record not found” message
Conflict (409) — “Already exists” message
Server errors (500) — “Something went wrong” with retry option
Loading state — disable buttons, show spinners during requests
Never show raw error messages like “HTTP 500” or stack traces to users. Always translate technical errors into human-readable messages. The API client is the right place to do this translation.
Errors are handled. The last piece of polish: loading states throughout your app — spinners, skeleton screens, and disabled buttons that make your app feel professional.