Python for JS Devs · FastAPI
Raise, don't return. And pick the right status — the AI-generated code you had used 404 where 409 belonged.
HTTPException — raise to respond with an errorIn Express you res.status(404).json(...) and hope you returned. In FastAPI you raise an HTTPException — execution stops immediately and FastAPI formats the error response.
if (!user) {
return res.status(404)
.json({ error: "not found" });
}from fastapi import HTTPException
if user is None:
raise HTTPException(status_code=404,
detail="User not found")No accidental fall-through: raise aborts the handler. Add headers= for things like WWW-Authenticate.
| Situation | Status |
|---|---|
| Created a resource | 201 |
| Bad/invalid input (auto by Pydantic) | 422 |
| Not authenticated | 401 |
| Authenticated but forbidden | 403 |
| Resource doesn't exist | 404 |
| Duplicate / state conflict | 409 |
Keep your service layer HTTP-agnostic (it raises the domain errors from Lesson 09), and translate to HTTP at the edge with an exception handler — so you don't sprinkle HTTPException through business logic.
class UserExistsError(Exception): ...
@app.exception_handler(UserExistsError)
def on_user_exists(request, exc):
return JSONResponse(status_code=409,
content={"detail": "User already exists"})
# service raises UserExistsError; handler maps it to 409 everywhere
HTTPException and validation error already renders as clean JSON with a detail field, and shows up in /docs. You're standardizing error shape for free.Write a POST /users that raises HTTPException(status_code=409, ...) when the email already exists in a fake dict, and 404 from a GET /users/{id} when missing. Test both in /docs and confirm the status codes. Correctly distinguishing 404 vs 409 is the win — it's the exact bug in your old code.
raise HTTPException(...) stops the handler and formats the error — no fall-through risk.409 Conflict. 404 means "not found" — the wrong choice here.422 Unprocessable Entity.