Path and Query Parameters

FastAPI lets you define parameters and variables that clients can include in the URL when making requests to an API or web application. These parameters can be used to query a database, sort and filter data, and many other things that shape the return response.

In this post we explore what path and query parameters are and how they work in FastAPI.

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. The GitHub named 01-up-and-running is where we’ll be starting.

Path Parameters

Path parameters are those that are defined in the URL’s path. In the example URL below, the number 1 could be used as a path parameter, which is then passed to the path operation function as an argument.

http://localhost:8000/flavors/1

Before continuing, let’s first create some basic data to work with.

In your project folder, create a new file named inventory.py and add the code below to it.

inventory = {
    1: {
        'flavor': 'Vanilla',
        'description': 'As plain as it gets.',
        'price': 2.99,
        'vegan': False,
        'stock': 20
    },
    2: {
        'flavor': 'Chocolate',
        'description': 'For chocolate lovers only.',
        'price': 3.99,
        'vegan': False,
        'stock': 22
    },
    3: {
        'flavor': 'Strawberry',
        'description': 'The perfect strawberry blend.',
        'price': 5.99,
        'vegan': False,
        'stock': 16
    },
    4: {
        'flavor': 'Mint Chocolate Chip',
        'description': 'Everyone\'s favorite flavor.',
        'price': 5.99,
        'vegan': False,
        'stock': 18
    },
    5: {
        'flavor': 'Peppermint',
        'description': 'The cool, candy cane flavor tickles the tongue.',
        'price': 8.99,
        'vegan': True,
        'stock': 12
    }
}

This creates a small dictionary of items that we can parse and output for our routes.

Next, update your main.py file to import the inventory and return it.

from fastapi import FastAPI
from inventory import inventory

app = FastAPI()

@app.get("/")
def root():
    return inventory

@app.get("/items/{id}")
def get_item(id: int):
    return inventory[id]

Now access the root path http://localhost:8000/. The entire ice cream inventory is returned.

{"1":{"flavor":"Vanilla","description":"As plain as it gets.","price":2.99,"vegan":false,"stock":20},"2":{"flavor":"Chocolate","description":"For chocolate lovers only.","price":3.99,"vegan":false,"stock":22},"3":{"flavor":"Strawberry","description":"The perfect strawberry blend.","price":5.99,"vegan":false,"stock":16},"4":{"flavor":"Mint Chocolate Chip","description":"Everyone's favorite flavor.","price":5.99,"vegan":false,"stock":18},"5":{"flavor":"Peppermint","description":"The cool, candy cane flavor tickles the tongue.","price":8.99,"vegan":true,"stock":12}}

In the /items/{id} route decorator, we use curly brackets to declare the path parameter id. This parameter gets passed down into the path operation function and we can use it as the key to fetch the corresponding item from the inventory dictionary.

Navigate to http://localhost:8000/items/1 to see a single item returned.

{"flavor":"Vanilla","description":"As plain as it gets.","price":2.99,"vegan":false,"stock":20}

The id parameter is restricted to only accepting integer values. If a string value is entered instead, such as http://localhost:8000/items/vanilla, FastAPI will return the following error message.

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

But what if someone tries to enter an ID that’s not a key in our dictionary?

Numeric Validation

FastAPI has the ability to validate data in the path parameters before passing it to our function. It accomplishes this with the Path class.

Update your main.py file to match the following.

from fastapi import FastAPI, Path
from inventory import inventory

app = FastAPI()

@app.get("/")
def root():
    return inventory

@app.get("/items/{id}")
def get_item(id: int = Path(ge=1, le=len(inventory))):
    return inventory[id]

We add the extra import for Path. Then in the path operation function, we create a number restraint on the flavor_id to only consist of values greater or equal to 1 AND less than or equal to the length of our inventory.

There are better ways to validate whether inventory items exists and we’ll get to that later, but for now, we’re just demonstrating how to use the Path class.

The following is a list of numeric validations you can set in the Path class.

  • gt: greater than
  • ge: greater than or equal
  • lt: less than
  • le: less than or equal

String Validation

You can also impose constraints on string parameters as well.

Add the following route to your main.py file.

@app.get("/flavors/{flavor}")
def get_flavor(flavor: str = Path(max_length=6)):
    return {"flavor":flavor}

The path is interpreting the parameter as a string instead of a number, and it’s setting the maximum length to be 6 characters. Navigate to http://localhost:8000/flavors/blue and you will see the following response.

{"flavor":"blue"}

But if you enter a longer number that contains more than 6 characters, like http://localhost:8000/flavors/vanilla, the following error message is returned.

{"detail":[{"loc":["path","flavor"],"msg":"ensure this value has at most 6 characters","type":"value_error.any_str.max_length","ctx":{"limit_value":6}}]}

Other constraints can be added to parameters such as minimum length and regular expressions. View the FastAPI documentation to learn more.

Multiple Path Parameters

Multiple path parameters can be specified in URL paths when creating more complex routes in larger projects.

Update the last route to be the following.

@app.get("/items/{brand}/{id}")
def get_flavor(brand: str, id: int):
    return {"brand": brand, "id":id}

Navigate to http://localhost:8000/items/baskin-robbins/1 to see the following returned.

{"brand":"baskin-robbins","id":1}

Order of Path Operations

You may find yourself in a situation where you want a fixed path that works alongside a path parameter. In those cases, define the fixed paths before the parameter paths.

For example, update your main.py file to match the following.

from fastapi import FastAPI, Path
from inventory import inventory

app = FastAPI()

@app.get("/")
def root():
    return inventory

@app.get("/items/{id}")
def get_item(id: int = Path(ge=1, le=len(inventory))):
    return inventory[id]

@app.get("/flavors/organic")
def get_organic():
    return {"flavors":"all organic flavors"}

@app.get("/flavors/{flavor_id}")
def get_flavor(flavor_id: int):
    return {"id":flavor_id}

When a client accesses http://localhost:8000/flavors/organic the get_organic() function is executed and returns the following.

{"flavors":"all organic flavors"}

However, if you move the /flavors/{flavor_id} route on top of /flavors/organic, the same request returns the Type Error shown below.

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

Keep in mind what order you are defining your application routes. The order could produce different results.

Query Parameters

Query parameters are those that not part of the path parameter. They appear after a ? symbol as a set of key-value pairs, and if multiple pairs are present they are separated by the & character.

Study the URL below.

http://localhost:8000/items/?vegan=True&max_price=10

The query parameters are vegan and max_price and they have the values True and 10. To parse those values, update your main.py to match the following.

from fastapi import FastAPI, Path
from inventory import inventory

app = FastAPI()

@app.get("/")
def root():
    return inventory

@app.get("/items/")
def get_items(vegan: bool = False, max_price: float = 20.0):
    results = {}
    counter = 1
    for item in inventory.values():
        if item["vegan"] == vegan and item["price"] <= max_price:
            results[counter] = item
            counter += 1
    return results

@app.get("/items/{id}")
def get_item(id: int):
    return inventory[id]

Navigate to http://localhost:8000/items/?vegan=True&max_price=10 and the following result is returned.

{"1":{"flavor":"Peppermint","description":"The cool, candy cane flavor tickles the tongue.","price":8.99,"vegan":true,"stock":12}}

If we set the max_price parameter to 2, we get an empty dictionary returned since there are no ice cream items that match both parameters.

Query Parameter Constraints

Query parameters can be assigned constraints just like path parameters. In the case above, maybe we would limit the max_price parameter to only accept positive integers.

The process is the same as with Path constraints but with a different import. Add Query to your fastapi imports as shown below.

from fastapi import FastAPI, Path, Query

Then update the get_items() path function to use the Query class and set the appropriate options.

@app.get("/items/")
def get_items(vegan: bool = False, max_price: float = Query(default=20, ge=1)):
    results = {}
    counter = 1
    for item in inventory.values():
        if item["vegan"] == vegan and item["price"] <= max_price:
            results[counter] = item
            counter += 1
    return results

Enter a negative value for the max_price parameter now returns the following error message.

{"detail":[{"loc":["query","max_price"],"msg":"ensure this value is greater than or equal to 1","type":"value_error.number.not_ge","ctx":{"limit_value":1}}]}

Check out the FastAPI documentation for Query Parameters and String Validations to see what else you can do with query parameters.

Path vs. Query Parameters

Both path and query parameters allow you, the developer, to receive variable input, process it, and return a response based on that input.

When designing API routes, there are times when you could use either a path or a query parameter to accomplish the same task. The following are examples of this.

# Get the flavor with ID 1
http://localhost:8000/flavors/1
http://localhost:8000/flavors/?id=1

# Get flavors on page 2
http://localhost:8000/flavors/page/2
http://localhost:8000/flavors/?page=2

However, path and query parameters have specific use cases that are more suitable for each method.

Path parameters are more suited to creating requests to specific information. The URL http://localhost:8000/flavors/1 returns a specific resource from the API. In this case, the ice cream flavor with an ID of 1.

Query parameters are better for filtering and sorting data. Examples of query parameters are shown below.

# get all organic ice cream
http://localhost:8000/flavors/?in_stock=true&vegan=True

# get ice cream that costs less than 20 and sort by price
http://localhost:8000/flavors/?max_price=20&order_by=price

The first URL would filter for all flavors that are in stock and are organic.

The second URL would return all flavors sorted by price and that have a price of less than 20.

Summary

In this post we learned what path and query parameters are and how to implement them in FastAPI. We then looked at how to create constraints on those parameters by limiting the numeric and string values. We also reviewed the main differences between the path and query parameters and when it’s best to use one over the other.

The code for this lesson is located in the GitHub repository in the folder named 02-path-and-query-parameters.

In the next lesson on FastAPI we introduce HTTP Exceptions and how to raise exceptions when something goes wrong in our API.