This article discusses the importance of route ordering in FastAPI to prevent unexpected behavior and type validation errors.
In this lesson, we explore a common pitfall related to route ordering in FastAPI. FastAPI matches incoming requests based on the defined routes, and the sequence in which the routes are declared is crucial for ensuring that each request is handled by its intended endpoint. Misordering routes can lead to unexpected behavior.
Below is a simple GET endpoint that retrieves a post by its ID. When a request is made to this route, it prints and returns the corresponding post details.
Copy
Ask AI
@app.get("/posts/{id}")def get_post(id: int): post = find_post(id) print(post) return {"post_detail": post}
Now, consider adding another GET endpoint intended to retrieve the latest post. The goal is to process a request to /posts/latest and return the most recent post.
The code evolves to determine the latest post by subtracting one from the length of the posts list. In this version, the routes are defined as follows:
If you change the request URL to /posts/latest, you might see the following error response:
Copy
Ask AI
{ "detail": [ { "loc": [ "path", "id" ], "msg": "value is not a valid integer", "type": "type_error.integer" } ]}
This error occurs because FastAPI processes routes in the order they are defined. The route /posts/{id} appears before /posts/latest, causing the string “latest” to be interpreted as the integer parameter id. Since “latest” is not an integer, FastAPI throws a type validation error.
Consider the following set of route definitions that illustrate the issue:
Copy
Ask AI
@app.get("/posts")def get_posts(): return {"data": my_posts}@app.post("/posts")def create_posts(post: Post): post_dict = post.dict() post_dict['id'] = randrange(0, 100000) my_posts.append(post_dict) return {"data": post_dict}@app.get("/posts/{id}")def get_post(id: int): post = find_post(id) print(post) return {"post_detail": post}@app.get("/posts/latest")def latest_post(): # Intended to return the latest post post = my_posts[len(my_posts)-1] return {"detail": post}
In this configuration, the route /posts/{id} catches any requests to /posts/..., including /posts/latest, because “latest” fits the dynamic segment {id}. FastAPI attempts to convert “latest” to an integer, resulting in the error.
To resolve this issue, ensure that the specific route (/posts/latest) is declared before the dynamic route (/posts/{id}). Here is the corrected ordering:
With this ordering, a GET request to /posts/latest is handled correctly by its dedicated endpoint, while requests for posts by integer ID are processed separately.
When accessing a specific post by its ID, a successful response might look like this:
Copy
Ask AI
{ "post_detail": { "title": "title of post 1", "content": "content of post 1", "id": 1 }}
For the latest post endpoint, a successful response might appear as follows:
Copy
Ask AI
{ "detail": { "title": "favorite foods", "content": "I like pizza", "id": 2 }}
Remember that FastAPI evaluates routes in the order they are added. Always define fixed routes (e.g., /posts/latest) before dynamic ones (e.g., /posts/{id}) to prevent unintended matches and to avoid type validation errors.