fromfastapiimportFastAPI,HTTPExceptionapp=FastAPI()items={"foo":"The Foo Wrestlers"}@app.get("/items/{item_id}")asyncdefread_item(item_id:str):ifitem_idnotinitems:raiseHTTPException(status_code=404,detail="Item not found")return{"item":items[item_id]}
HTTPException is a normal Python exception with additional data relevant for APIs.
Because it's a Python exception, you don't return it, you raise it.
This also means that if you are inside a utility function that you are calling inside of your path operation function, and you raise the HTTPException from inside of that utility function, it won't run the rest of the code in the path operation function, it will terminate that request right away and send the HTTP error from the HTTPException to the client.
The benefit of raising an exception over returning a value will be more evident in the section about Dependencies and Security.
In this example, when the client requests an item by an ID that doesn't exist, raise an exception with a status code of 404:
fromfastapiimportFastAPI,HTTPExceptionapp=FastAPI()items={"foo":"The Foo Wrestlers"}@app.get("/items/{item_id}")asyncdefread_item(item_id:str):ifitem_idnotinitems:raiseHTTPException(status_code=404,detail="Item not found")return{"item":items[item_id]}
If the client requests http://example.com/items/foo (an item_id"foo"), that client will receive an HTTP status code of 200, and a JSON response of:
{"item":"The Foo Wrestlers"}
But if the client requests http://example.com/items/bar (a non-existent item_id"bar"), that client will receive an HTTP status code of 404 (the "not found" error), and a JSON response of:
{"detail":"Item not found"}
Tip
When raising an HTTPException, you can pass any value that can be converted to JSON as the parameter detail, not only str.
You could pass a dict, a list, etc.
They are handled automatically by FastAPI and converted to JSON.
There are some situations in where it's useful to be able to add custom headers to the HTTP error. For example, for some types of security.
You probably won't need to use it directly in your code.
But in case you needed it for an advanced scenario, you can add custom headers:
fromfastapiimportFastAPI,HTTPExceptionapp=FastAPI()items={"foo":"The Foo Wrestlers"}@app.get("/items-header/{item_id}")asyncdefread_item_header(item_id:str):ifitem_idnotinitems:raiseHTTPException(status_code=404,detail="Item not found",headers={"X-Error":"There goes my error"},)return{"item":items[item_id]}
Let's say you have a custom exception UnicornException that you (or a library you use) might raise.
And you want to handle this exception globally with FastAPI.
You could add a custom exception handler with @app.exception_handler():
fromfastapiimportFastAPI,Requestfromfastapi.responsesimportJSONResponseclassUnicornException(Exception):def__init__(self,name:str):self.name=nameapp=FastAPI()@app.exception_handler(UnicornException)asyncdefunicorn_exception_handler(request:Request,exc:UnicornException):returnJSONResponse(status_code=418,content={"message":f"Oops! {exc.name} did something. There goes a rainbow..."},)@app.get("/unicorns/{name}")asyncdefread_unicorn(name:str):ifname=="yolo":raiseUnicornException(name=name)return{"unicorn_name":name}
Here, if you request /unicorns/yolo, the path operation will raise a UnicornException.
But it will be handled by the unicorn_exception_handler.
So, you will receive a clean error, with an HTTP status code of 418 and a JSON content of:
{"message":"Oops! yolo did something. There goes a rainbow..."}
Technical Details
You could also use from starlette.requests import Request and from starlette.responses import JSONResponse.
FastAPI provides the same starlette.responses as fastapi.responses just as a convenience for you, the developer. But most of the available responses come directly from Starlette. The same with Request.
When a request contains invalid data, FastAPI internally raises a RequestValidationError.
And it also includes a default exception handler for it.
To override it, import the RequestValidationError and use it with @app.exception_handler(RequestValidationError) to decorate the exception handler.
The exception handler will receive a Request and the exception.
fromfastapiimportFastAPI,HTTPExceptionfromfastapi.exceptionsimportRequestValidationErrorfromfastapi.responsesimportPlainTextResponsefromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPExceptionapp=FastAPI()@app.exception_handler(StarletteHTTPException)asyncdefhttp_exception_handler(request,exc):returnPlainTextResponse(str(exc.detail),status_code=exc.status_code)@app.exception_handler(RequestValidationError)asyncdefvalidation_exception_handler(request,exc):returnPlainTextResponse(str(exc),status_code=400)@app.get("/items/{item_id}")asyncdefread_item(item_id:int):ifitem_id==3:raiseHTTPException(status_code=418,detail="Nope! I don't like 3.")return{"item_id":item_id}
Now, if you go to /items/foo, instead of getting the default JSON error with:
{"detail":[{"loc":["path","item_id"],"msg":"value is not a valid integer","type":"type_error.integer"}]}
you will get a text version, with:
1 validation error
path -> item_id
value is not a valid integer (type=type_error.integer)
These are technical details that you might skip if it's not important for you now.
RequestValidationError is a sub-class of Pydantic's ValidationError.
FastAPI uses it so that, if you use a Pydantic model in response_model, and your data has an error, you will see the error in your log.
But the client/user will not see it. Instead, the client will receive an "Internal Server Error" with a HTTP status code 500.
It should be this way because if you have a Pydantic ValidationError in your response or anywhere in your code (not in the client's request), it's actually a bug in your code.
And while you fix it, your clients/users shouldn't have access to internal information about the error, as that could expose a security vulnerability.
The same way, you can override the HTTPException handler.
For example, you could want to return a plain text response instead of JSON for these errors:
fromfastapiimportFastAPI,HTTPExceptionfromfastapi.exceptionsimportRequestValidationErrorfromfastapi.responsesimportPlainTextResponsefromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPExceptionapp=FastAPI()@app.exception_handler(StarletteHTTPException)asyncdefhttp_exception_handler(request,exc):returnPlainTextResponse(str(exc.detail),status_code=exc.status_code)@app.exception_handler(RequestValidationError)asyncdefvalidation_exception_handler(request,exc):returnPlainTextResponse(str(exc),status_code=400)@app.get("/items/{item_id}")asyncdefread_item(item_id:int):ifitem_id==3:raiseHTTPException(status_code=418,detail="Nope! I don't like 3.")return{"item_id":item_id}
Technical Details
You could also use from starlette.responses import PlainTextResponse.
FastAPI provides the same starlette.responses as fastapi.responses just as a convenience for you, the developer. But most of the available responses come directly from Starlette.
You will receive a response telling you that the data is invalid containing the received body:
{"detail":[{"loc":["body","size"],"msg":"value is not a valid integer","type":"type_error.integer"}],"body":{"title":"towel","size":"XL"}}
FastAPI's HTTPException vs Starlette's HTTPException¶
FastAPI has its own HTTPException.
And FastAPI's HTTPException error class inherits from Starlette's HTTPException error class.
The only difference, is that FastAPI's HTTPException allows you to add headers to be included in the response.
This is needed/used internally for OAuth 2.0 and some security utilities.
So, you can keep raising FastAPI's HTTPException as normally in your code.
But when you register an exception handler, you should register it for Starlette's HTTPException.
This way, if any part of Starlette's internal code, or a Starlette extension or plug-in, raises a Starlette HTTPException, your handler will be able to catch and handle it.
In this example, to be able to have both HTTPExceptions in the same code, Starlette's exceptions is renamed to StarletteHTTPException:
If you want to use the exception along with the same default exception handlers from FastAPI, You can import and re-use the default exception handlers from fastapi.exception_handlers:
fromfastapiimportFastAPI,HTTPExceptionfromfastapi.exception_handlersimport(http_exception_handler,request_validation_exception_handler,)fromfastapi.exceptionsimportRequestValidationErrorfromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPExceptionapp=FastAPI()@app.exception_handler(StarletteHTTPException)asyncdefcustom_http_exception_handler(request,exc):print(f"OMG! An HTTP error!: {repr(exc)}")returnawaithttp_exception_handler(request,exc)@app.exception_handler(RequestValidationError)asyncdefvalidation_exception_handler(request,exc):print(f"OMG! The client sent invalid data!: {exc}")returnawaitrequest_validation_exception_handler(request,exc)@app.get("/items/{item_id}")asyncdefread_item(item_id:int):ifitem_id==3:raiseHTTPException(status_code=418,detail="Nope! I don't like 3.")return{"item_id":item_id}
In this example you are just printing the error with a very expressive message, but you get the idea. You can use the exception and then just re-use the default exception handlers.