Every endpoint so far has assumed the happy path. But real APIs have to say "no" sometimes: the blog you asked for does not exist, you are not allowed here, that ID is wrong. For these, you return a status code in the 400s, and FastAPI gives you a clean tool to do it: HTTPException.
Raising an HTTPException
Import it, then raise it when something is off. Here we fetch a blog from a small dict and 404 if the ID is missing.
from fastapi import FastAPI, HTTPExceptionapp = FastAPI()blogs = {1: "My First Blog", 2: "Hello FastAPI"}@app.get("/blog/{blog_id}")async def read_blog(blog_id: int): if blog_id not in blogs: raise HTTPException(status_code=404, detail="Blog not found") return {"blog_id": blog_id, "title": blogs[blog_id]}Let's understand it first:
You
raiseanHTTPException, you do notreturnit. It is a Python exception, so raising it stops the function right there.status_code=404is the "Not Found" code from the status code post.detailis the message the client sees.Ask for
/blog/1and you get the blog. Ask for/blog/99and you get a 404 with{"detail": "Blog not found"}.
Because it is a real exception, raising it from inside a helper function works too. The request ends immediately and the error goes straight to the client.
Adding custom headers
For some cases, mostly security, you may want to attach headers to the error. Pass a headers dict.
@app.get("/blog/{blog_id}")async def read_blog(blog_id: int): if blog_id not in blogs: raise HTTPException( status_code=404, detail="Blog not found", headers={"X-Error": "No such blog"}, ) return {"blog_id": blog_id, "title": blogs[blog_id]}You rarely need this day to day, but it is there when an advanced scenario calls for it.
A custom exception handler
For app-wide handling of your own exception types, register a handler with @app.exception_handler(). FastAPI catches that exception anywhere and runs your function.
from fastapi import FastAPI, Requestfrom fastapi.responses import JSONResponseclass BlogLockedError(Exception): def __init__(self, blog_id: int): self.blog_id = blog_idapp = FastAPI()@app.exception_handler(BlogLockedError)async def blog_locked_handler(request: Request, exc: BlogLockedError): return JSONResponse(status_code=423, content={"message": f"Blog {exc.blog_id} is locked"})Now raising BlogLockedError(5) anywhere returns a tidy 423 response with your message. One handler, consistent errors across the whole app.
Gotcha: raise, never return, an HTTPException. Returning it just hands the client a weird serialized object with a 200 status, which is the opposite of what you want. The raise is what makes FastAPI turn it into a proper error response. 😁
