Python for JS Devs · FastAPI

Errors & status codes

Raise, don't return. And pick the right status — the AI-generated code you had used 404 where 409 belonged.

Lesson 15Time ~12 min Win Map a domain error to the right statusPrereq Lessons 09, 14

1 · HTTPException — raise to respond with an error

In 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.

Express
if (!user) {
  return res.status(404)
    .json({ error: "not found" });
}
FastAPI
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.

2 · Pick the correct status code

Real fix from your old code The AI-generated register handler raised 404 for "user already exists". Wrong: 404 means not found. A duplicate is a 409 Conflict. Getting this right is what separates a real API from a toy.
SituationStatus
Created a resource201
Bad/invalid input (auto by Pydantic)422
Not authenticated401
Authenticated but forbidden403
Resource doesn't exist404
Duplicate / state conflict409

3 · Domain errors → HTTP, cleanly

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
Bonus Every 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.

🎯 Your win

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.

Check yourself

Q1. To return an error response, a handler should:
raise HTTPException(...) stops the handler and formats the error — no fall-through risk.
Q2. Trying to create a duplicate resource should return:
Duplicates are 409 Conflict. 404 means "not found" — the wrong choice here.
Q3. Invalid request body validation errors return, by default:
Pydantic validation failures auto-produce 422 Unprocessable Entity.
Primary source FastAPI — Handling Errors. Covers HTTPException and custom handlers.
💬 Ask your teacher: should validation errors really be 422 or 400? There's genuine debate — ask and I'll give you the reasoning and how to override it.