HTTP Exceptions in FastAPI

Sometimes things don’t always work as expected. What happens when the API is not able to find the data requested? Or what if the client doesn’t have access to the specified resource? We need a way to notify the client that something went wrong.

In this post we look at how FastAPI can raise HTTP Exceptions and return error messages and status codes to the client.

The contents of this post are a continuation of a series of posts on FastAPI. Visit the FastAPI page to see what has been covered already. There is a GitHub repository for this project. The folder named 03-basic-routing is where we’ll be starting.

HTTP Status Codes

HTTP status codes represent the result of the specified request. They are included in the headers of every FastAPI response and represent information, success, or problems with the request.

Status codes are grouped into five different classes depending on the type of information the API or server is communicating.

Class Status code range
Informational responses 100-199
Successful responses 200-299
Redirection responses 300-399
Client error responses 400-499
Server error responses 500-599

The most common status codes you might see are:

  • 200 - OK
  • 301 - Permanent Redirect
  • 404 - Not Found
  • 403 - Forbidden
  • 500 - Internal Server Error
  • 503 - Service Unavailable

Built-in Exceptions

FastAPI will automatically generate errors and status codes for common problems.

404 Not Found

The status code 404 is well known error. It’s returned when the request page or resource is not found.

Open up your project and start the web server. If you need the code for this, visit the GitHub repository and run the project in the 03-basic-routing folder.

Use a browser and access a non-existent path such as http://localhost:8000/prices to see the following message returned.

{"detail":"Not Found"}

Tools like curl, wget, or Postman will show the full response.

With curl, enter the following command in Terminal.

curl -v http://localhost:8000/prices

The -v option stands for “verbose” and is used to display a more detailed version of the response. See the response below.

*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8000 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /prices HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.64.1
> Accept: */*
> 
< HTTP/1.1 404 Not Found
< date: Tue, 27 Sep 2022 14:58:31 GMT
< server: uvicorn
< content-length: 22
< content-type: application/json
< 
* Connection #0 to host localhost left intact
{"detail":"Not Found"}* Closing connection 0

422 Unprocessable Entity

Another exception FastAPI auto-generates is a 422 Unprocessable Entity. This can occur when you send the wrong type of data with the request. This can be demonstrated with incorrect path parameters.

Navigate to the following URLs.

http://localhost:8000/items/1
http://localhost:8000/items/vanilla

The first URL works as expected. However, the second URL returns the an error message. FastAPI, with the help of Pydantic, parses the data but does not know how to convert the word “vanilla” into a numeric value.

{"detail":[{"loc":["path","id"],"msg":"value is not a valid integer","type":"type_error.integer"}]}

If you run the following curl command you can see the status code in the response.

curl -v http://localhost:8000/flavors/vanilla

The response is:

*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8000 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /flavors/vanilla HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.64.1
> Accept: */*
> 
< HTTP/1.1 422 Unprocessable Entity
< date: Tue, 27 Sep 2022 15:11:28 GMT
< server: uvicorn
< content-length: 99
< content-type: application/json
< 
* Connection #0 to host localhost left intact
{"detail":[{"loc":["path","id"],"msg":"value is not a valid integer","type":"type_error.integer"}]}* Closing connection 0

500 Internal Server Error

What happens if the API code itself has bugs?

If we pass a valid number in the request but our dictionary does not contain that key item we get a 500 Internal Server Error. This is due to a Python KeyError exception that’s not being handled.

Navigate to http://localhost:8000/items/100/ to see this error yourself.

For this error, we can create additional checks to see if the data exists. If nothing is found, we can manually raise exceptions to inform the client what exactly happened.

Raise Your Own Exception

FastAPI provides an HTTPException class that allows you to raise your own exception errors. This is similar to regular exception handling in Python but with additional data more relevant for APIs.

Add HTTPException to your list of fastapi imports.

from fastapi import FastAPI, HTTPException

Then update the GET route that handles single item requests:

@app.get("/items/{id}/")
def get_item(id: int):
    if id not in inventory:
        raise HTTPException(status_code=404, detail="Ice cream ID not found")
    return inventory[id]

Now when you try to access an ID that does not exist, the following message is returned.

{"detail":"Ice cream ID not found"}

We send the client a 404 Not Found message by setting the status_code parameter to 404. This is much more informative to the client than a “Internal Server” error. You can also add the if-statement to the PUT and DELETE routes to ensure those paths will work too.

@app.put("/items/{id}/")
def update_item(id: int, item: Item):
    if id not in inventory:
        raise HTTPException(status_code=404, detail="Ice cream ID not found")
    inventory[id] = item.dict()
    return inventory

@app.delete("/items/{id}/")
def delete_item(id: int):
    if id not in inventory:
        raise HTTPException(status_code=404, detail="Ice cream ID not found")
    del inventory[id]
    return inventory

You can visit the Mozilla developer documentation to see a list of all possible status code errors. It’s a good idea to know what’s available when building out your applications.

Convenience Variables

Keep in mind that status code numbers are not always informative by themselves. If you send the client a 418 error, would you remember what that does?

FastAPI has a status module that provides a collection of variables with more descriptive names.

Add status to the list of FastAPI imports.

from fastapi import FastAPI, HTTPException, status

Then update your exceptions with the status_code parameter as shown below.

raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ice cream ID not found")

Developers appreciate these convenience variables when they’re scanning a heap of code every day. The meaning of the error is more quickly understood.

Summary

In this post, we learned a little about HTTP Status Codes and how to work with them in FastAPI. In future lessons we explore status codes with FastAPI’s authentication and other features.

To view more FastAPI lessons, visit the FastAPI reading list.