Handling Errors¶
There are many situations in which you need to notify an error to a client that is using your API.
This client could be a browser with a frontend, a code from someone else, an IoT device, etc.
You could need to tell the client that:
- The client doesn't have enough privileges for that operation.
- The client doesn't have access to that resource.
- The item the client was trying to access doesn't exist.
- etc.
In these cases, you would normally return anHTTP status code in the range of400 (from 400 to 499).
This is similar to the 200 HTTP status codes (from 200 to 299). Those "200" status codes mean that somehow there was a "success" in the request.
The status codes in the 400 range mean that there was an error from the client.
Remember all those"404 Not Found" errors (and jokes)?
UseHTTPException¶
To return HTTP responses with errors to the client you useHTTPException.
ImportHTTPException¶
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]}Raise anHTTPException in your code¶
HTTPException is a normal Python exception with additional data relevant for APIs.
Because it's a Python exception, you don'treturn it, youraise it.
This also means that if you are inside a utility function that you are calling inside of yourpath operation function, and you raise theHTTPException from inside of that utility function, it won't run the rest of the code in thepath operation function, it will terminate that request right away and send the HTTP error from theHTTPException 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 of404:
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]}The resulting response¶
If the client requestshttp://example.com/items/foo (anitem_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 requestshttp://example.com/items/bar (a non-existentitem_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 anHTTPException, you can pass any value that can be converted to JSON as the parameterdetail, not onlystr.
You could pass adict, alist, etc.
They are handled automatically byFastAPI and converted to JSON.
Add custom headers¶
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]}Install custom exception handlers¶
You can add custom exception handlers withthe same exception utilities from Starlette.
Let's say you have a custom exceptionUnicornException that you (or a library you use) mightraise.
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, thepath operation willraise aUnicornException.
But it will be handled by theunicorn_exception_handler.
So, you will receive a clean error, with an HTTP status code of418 and a JSON content of:
{"message":"Oops! yolo did something. There goes a rainbow..."}Technical Details
You could also usefrom starlette.requests import Request andfrom starlette.responses import JSONResponse.
FastAPI provides the samestarlette.responses asfastapi.responses just as a convenience for you, the developer. But most of the available responses come directly from Starlette. The same withRequest.
Override the default exception handlers¶
FastAPI has some default exception handlers.
These handlers are in charge of returning the default JSON responses when youraise anHTTPException and when the request has invalid data.
You can override these exception handlers with your own.
Override request validation exceptions¶
When a request contains invalid data,FastAPI internally raises aRequestValidationError.
And it also includes a default exception handler for it.
To override it, import theRequestValidationError and use it with@app.exception_handler(RequestValidationError) to decorate the exception handler.
The exception handler will receive aRequest 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:RequestValidationError):message="Validation errors:"forerrorinexc.errors():message+=f"\nField:{error['loc']}, Error:{error['msg']}"returnPlainTextResponse(message,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:
Validation errors:Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integerOverride theHTTPException error handler¶
The same way, you can override theHTTPException 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:RequestValidationError):message="Validation errors:"forerrorinexc.errors():message+=f"\nField:{error['loc']}, Error:{error['msg']}"returnPlainTextResponse(message,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 usefrom starlette.responses import PlainTextResponse.
FastAPI provides the samestarlette.responses asfastapi.responses just as a convenience for you, the developer. But most of the available responses come directly from Starlette.
Warning
Have in mind that theRequestValidationError contains the information of the file name and line where the validation error happens so that you can show it in your logs with the relevant information if you want to.
But that means that if you just convert it to a string and return that information directly, you could be leaking a bit of information about your system, that's why here the code extracts and shows each error independently.
Use theRequestValidationError body¶
TheRequestValidationError contains thebody it received with invalid data.
You could use it while developing your app to log the body and debug it, return it to the user, etc.
fromfastapiimportFastAPI,Requestfromfastapi.encodersimportjsonable_encoderfromfastapi.exceptionsimportRequestValidationErrorfromfastapi.responsesimportJSONResponsefrompydanticimportBaseModelapp=FastAPI()@app.exception_handler(RequestValidationError)asyncdefvalidation_exception_handler(request:Request,exc:RequestValidationError):returnJSONResponse(status_code=422,content=jsonable_encoder({"detail":exc.errors(),"body":exc.body}),)classItem(BaseModel):title:strsize:int@app.post("/items/")asyncdefcreate_item(item:Item):returnitemNow try sending an invalid item like:
{"title":"towel","size":"XL"}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'sHTTPException vs Starlette'sHTTPException¶
FastAPI has its ownHTTPException.
AndFastAPI'sHTTPException error class inherits from Starlette'sHTTPException error class.
The only difference is thatFastAPI'sHTTPException accepts any JSON-able data for thedetail field, while Starlette'sHTTPException only accepts strings for it.
So, you can keep raisingFastAPI'sHTTPException as normally in your code.
But when you register an exception handler, you should register it for Starlette'sHTTPException.
This way, if any part of Starlette's internal code, or a Starlette extension or plug-in, raises a StarletteHTTPException, your handler will be able to catch and handle it.
In this example, to be able to have bothHTTPExceptions in the same code, Starlette's exceptions is renamed toStarletteHTTPException:
fromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPExceptionReuseFastAPI's exception handlers¶
If you want to use the exception along with the same default exception handlers fromFastAPI, you can import and reuse the default exception handlers fromfastapi.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 reuse the default exception handlers.







