DEV Community

Cover image for FastAPI Part 2: Routing, Path Parameters, and Query Parameters
James
James

Posted on • Updated on

FastAPI Part 2: Routing, Path Parameters, and Query Parameters

GitHub Repo

The Basics of Routing

Routing in web development refers to the process of directing a request to the correct handler based on the URL and HTTP method (GET, POST, etc.). In FastAPI, routes are defined using decorators that specify the HTTP method for the function they decorate. These routes act as endpoints for API users to access different resources.

Using Decorators to Define Routes

FastAPI utilizes a straightforward yet powerful system of decorators to map specific functions in your Python code to HTTP routes. These decorators make it incredibly easy to connect your logic to the corresponding HTTP methods, streamlining the creation of RESTful interfaces. Here’s an expanded look at how these decorators work and some of the additional parameters you can use to fine-tune your API's behavior.

Common HTTP Method Decorators

  • @app.get(): This decorator defines routes that accept GET requests. GET requests are generally used to retrieve data, whether fetching a single item by ID or a list of items based on query parameters. The data returned by GET routes is not supposed to alter the state of the application's data on the server.
  • @app.post(): POST requests are designed for creating new resources. When using the @app.post() decorator, your API expects to receive data (usually through the request body), which it will use to create a new item in the database or another storage system.
  • @app.put(): Put requests are used for updating existing resources. The @app.put() decorator helps you define routes that replace an existing item with a new one provided in the request body or update specific fields of an existing item.
  • @app.delete(): This decorator defines a route to handle DELETE requests, which are used to remove existing resources from your system.

Each of these decorators takes the route path as an argument along with optional parameters to control additional details, such as response status codes or tags. Here is a basic example:

@app.get("/")
async def home():
    return {"message": "Welcome to our API!"}
Enter fullscreen mode Exit fullscreen mode

Additional Parameters for Decorators

While the primary purpose of these decorators is to link a URL path to a Python function and assign an HTTP method, they also accept various optional parameters that allow you to control additional aspects of the request and response:

  • Status Codes: You can specify the HTTP status code to return for responses from this route. For example, using status_code=201 in a @app.post() decorator is common to indicate that a resource was created successfully.
  • Tags: Tags help you organize endpoints into groups in the generated documentation, making it easier for developers to navigate the API.
  • Responses: You can define custom response classes that dictate how the response should be formatted. This can be useful for setting cache controls, media types, and other headers.
  • Dependencies: Dependencies can be declared for individual operations. These are execution dependencies or security dependencies that must be met before the actual route function is called.

Example with Advanced Decorator Use

Here’s a more advanced example using some of these optional parameters:

from fastapi import status

@app.post("/items/", status_code=status.HTTP_201_CREATED, 
          tags=["Item Operations"])
async def create_item(item: Item):
    """
    Create an item in the database.
    """
    db.add(item)
    return item

Enter fullscreen mode Exit fullscreen mode

In this example:

  • The route is set to handle POST requests aimed at creating a new item.
  • It uses the status_code parameter to explicitly set the HTTP status code to 201, which clients interpret as "Created."
  • The route is tagged as "Item Operations," which helps organize this endpoint under a specific group in the API documentation.

Understanding and utilizing these decorator options in FastAPI not only helps in creating a functional API but also enhances its maintainability and usability, especially in complex systems where good documentation and clear organization are important.

Path Parameters

Path parameters, also known as route parameters, are dynamic parts of the URL path that are used to capture specific values from the URL. They are essential for creating RESTful APIs that respond to specific resources or actions based on the URL.

Capturing Dynamic Segments in the URL

FastAPI allows you to define routes that can dynamically accept parts of the URL as parameters. This functionality is particularly useful when you need to perform actions on specific resources, like retrieving, updating, or deleting a particular user or item based on an ID.

Defining Path Parameters

To define a path parameter in FastAPI, you include a variable in the route's path enclosed in curly braces {}. Here’s how you can define and use a path parameter in a FastAPI route:

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    # FastAPI automatically extracts `user_id` from the URL path and injects it into the function as an integer.
    return {"user_id": user_id}
Enter fullscreen mode Exit fullscreen mode

In this example, user_id is a path parameter integrated directly into the URL path. When a request is made to this endpoint, FastAPI captures the user_id segment of the URL, converts it to an integer (thanks to the type hint), and passes it to the get_user function.

Automatic Data Conversion

FastAPI utilizes Python's type hints to automatically convert URL parameters to the specified types. This not only simplifies the function implementation by ensuring that parameters are of the expected type but also adds a layer of validation:

  • If the parameter can be successfully converted to the specified type, FastAPI passes it to your function.
  • If the conversion fails (e.g., a non-integer string is passed where an integer is expected), FastAPI will automatically respond with a 422 Unprocessable Entity error, indicating that something was wrong with the user input.

Handling Multiple Path Parameters

You can define routes that capture more than one path parameter. This is particularly useful for nested resources or more complex URL structures:

@app.get("/items/{category_id}/products/{product_id}")
async def get_product(category_id: int, product_id: int):
    return {"category_id": category_id, "product_id": product_id}
Enter fullscreen mode Exit fullscreen mode

In this route, both category_id and product_id are captured as path parameters. This method allows APIs to address resources hierarchically and contextually, enhancing the navigational logic within the API.

Benefits of Using Path Parameters

Using path parameters can improve the intuitiveness and ease of use of your API. Your API endpoints can reflect the hierarchical structure of your data, making it easier to understand how resources are organized and accessed. This structure is critical in the development of scalable and maintainable web applications that can handle complex data models and user interactions.

Type Hinting for Automatic Data Conversion

FastAPI takes advantage of Python's robust type hints to automatically handle data conversion, validation, and error handling. This functionality is especially useful when using path parameters and query parameters since it guarantees that the data received by your functions is precisely what it's supposed to be.

How Type Hinting Works in FastAPI

When you define a function in FastAPI, any path or query parameters can be given a specific type through Python's type annotations. FastAPI uses these annotations to validate and convert incoming data before it ever reaches your function. Here's a closer look at the process:

  • Validation: FastAPI checks if the incoming parameters conform to the types specified in your function's signature. If a parameter is expected to be an integer (as specified by int), but the request provides a string that cannot be converted into an integer, FastAPI will block the request.
  • Conversion: If the validation is successful, FastAPI converts the parameter to the specified type. For example, if your route expects an int and the user provides a numeric string via the URL, FastAPI automatically converts this string to an integer.
  • Error Handling: If either validation or conversion fails, FastAPI does not call your function. Instead, it returns a 422 Unprocurable Entity response directly to the client. This response includes a JSON body that details what went wrong, helping the client correct their request.

This mechanism not only prevents common data-related bugs but also enhances security by ensuring that data types are enforced before any processing takes place.

Query Parameters

Query parameters allow clients to pass optional or additional information to API endpoints. They are used extensively in APIs for filtering, sorting, and pagination purposes.

Passing Optional Data in the URL

Query parameters are defined in FastAPI by setting default values in the function parameters. FastAPI interprets any default-value parameters in a route's function as query parameters. This design allows you to easily specify what data your API can accept without having to manually parse and handle URL queries.

Here’s how you can handle optional query parameters more effectively:

@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10, search: Optional[str] = None):
    items = fetch_items(skip=skip, limit=limit, search_query=search)
    return {"items": items, "skip": skip, "limit": limit}
Enter fullscreen mode Exit fullscreen mode

In this revised example:

  • skip and limit help with pagination, as in the previous example.
  • search is an optional parameter that clients can use to filter items based on a search query. If search is not provided, it defaults to None, which the fetch_items function can interpret as no filter applied.

Benefits of Using Query Parameters

Using query parameters in FastAPI allows for flexible, powerful APIs that can adapt to a variety of client needs:

  • Flexibility: Clients can modify their requests to include only the information they need, reducing bandwidth and processing time.
  • Functionality: APIs can offer extensive customizability, such as filtering, sorting, and precise pagination, enhancing user experience.
  • Simplicity: FastAPI's automatic handling of query parameters reduces boilerplate code, making the API easier to develop and maintain.

This concludes Part 2 of our FastAPI tutorial, where you learned about routing, path parameters, and query parameters. These fundamentals are essential for building robust and dynamic APIs. In the next part, we’ll explore how to handle requests and responses more deeply, looking at request bodies and response models. Stay tuned!


Code

from fastapi import FastAPI

app = FastAPI()

# Basic routing with an HTTP GET method to return a welcome message
@app.get("/")
async def home():
    return {"message": "Welcome to our API!"}

@app.post("/items/", status_code=status.HTTP_201_CREATED, tags=["Item Operations"])
async def create_item(item: Item):
    """
    Create an item in the database.
    """
    db.add(item)
    return item

# Defining a route that captures a path parameter
@app.get("/users/{user_id}")
async def get_user(user_id: int):
    # FastAPI automatically validates and converts `user_id` to an integer
    return {"user_id": user_id}

@app.get("/items/{category_id}/products/{product_id}")
async def get_product(category_id: int, product_id: int):
    return {"category_id": category_id, "product_id": product_id}

# Defining a route that uses query parameters to filter data (e.g., for pagination)
@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10, search: Optional[str] = None):
    items = fetch_items(skip=skip, limit=limit, search_query=search)
    return {"items": items, "skip": skip, "limit": limit}

# Running the API with Uvicorn. This command should be in a separate runner file or in the command line.
# `uvicorn main:app --reload` where `main` is the name of your Python file.

Enter fullscreen mode Exit fullscreen mode

Stay Tuned for Part 3

In the upcoming Part 3, we’ll delve deeper into one of FastAPI’s most powerful features: Pydantic Data Models. We will explore how Pydantic enhances data validation, parsing, and type safety, making your API development smoother and more error-proof.

We'll cover:

  • Why Pydantic? Understand the benefits of using Pydantic for data validation and type safety in your FastAPI applications.
  • Defining Pydantic Models: Learn how to create robust models using Pydantic’s BaseModel and explore advanced features such as field types, validators, and nested models.
  • Using Pydantic Models in API Endpoints: I
  • Integrating these models can simplify data handling, improve error handling, and ensure your API endpoints are type-safe and easy to understand.

Leave comments about what you would like me to cover next

If you would like to support me or buy me a beer feel free to join my Patreon jamesbmour

Top comments (1)

Collapse
 
aatmaj profile image
Aatmaj

Nice !