# Unit 7: Query Parameters

## Introduction

Query parameters are a fundamental aspect of web development, enabling the passing of optional or required information in GET requests to the server. They are appended to the URL following a `?` symbol and can be used to filter results, specify the nature of a request, or provide additional data **without changing the route of the request**.

***

## Query parameters

To define **query parameters** in FastAPI, you simply declare them as **function arguments** in your path operation functions. **FastAPI automatically recognizes them as query parameters if they are not part of the path parameters.** This is elegantly handled with Python's type hints, allowing developers to specify the data type of each query parameter, which FastAPI uses to validate incoming requests.

{% code lineNumbers="true" %}

```python
from fastapi import FastAPI

app = FastAPI()

sample_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 100):
    return sample_items_db[skip: skip + limit]
```

{% endcode %}

To evaluate the above program, you can visit the following two URLs:

* `http://127.0.0.1:8000/items/`
* `http://127.0.0.1:8000/items/?skip=0&limit=2`

For the first URL, the program will automatically set `skip=0` and `limit=100` because we've defined these default values in the parameters of the `read_item()` function. For the second URL, the result is as follows, due to the query parameters being set to `skip=0` and `limit=100`.

```json
[
    {
        "item_name": "Foo"
    },
    {
        "item_name": "Bar"
    }
]
```

### Optional parameters

FastAPI can distinguish the path parameters and query parameters in the same function, such as follows:

<pre class="language-python" data-line-numbers><code class="lang-python">from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(<a data-footnote-ref href="#user-content-fn-1">item_id</a>: str, <a data-footnote-ref href="#user-content-fn-2">q</a>: str | None = None):
    if q:
        return {"item_id": item_id, "q": q}
    return {"item_id": item_id}
</code></pre>

In the above program, `q` is an optional query parameter by setting their default to `None`.

### Query parameter type conversion

Let's take a look at the following program:

<pre class="language-python" data-line-numbers><code class="lang-python">from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(<a data-footnote-ref href="#user-content-fn-1">item_id</a>: str, <a data-footnote-ref href="#user-content-fn-2">q</a>: str | None = None, <a data-footnote-ref href="#user-content-fn-2">short</a>: bool = False):
    item = {"item_id": item_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update(
            {"description": "This is a long description"}
        )
    return item
</code></pre>

The following URLs will result in the same output:

* `http://127.0.0.1:8000/items/foo?short=1`
* `http://127.0.0.1:8000/items/foo?short=True`
* `http://127.0.0.1:8000/items/foo?short=true`
* `http://127.0.0.1:8000/items/foo?short=on`
* `http://127.0.0.1:8000/items/foo?short=yes`

### Multiple path and query parameters

You can declare multiple path parameters and query parameters at the same time, FastAPI knows which is which.

{% code lineNumbers="true" %}

```python
from fastapi import FastAPI

app = FastAPI()


@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(
    user_id: int, item_id: str, q: str | None = None, short: bool = False
):
    item = {"item_id": item_id, "owner_id": user_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update(
            {"description": "This is a long description"}
        )
    return item
```

{% endcode %}

### Required query parameters

For non-path parameters like query parameters, a default value makes them optional. Use `None` if you just want them to be optional without a specific value. To make a parameter required, simply omit the default value.

<pre class="language-python" data-line-numbers><code class="lang-python">from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def get_item(<a data-footnote-ref href="#user-content-fn-1">item_id</a>: str, <a data-footnote-ref href="#user-content-fn-2">short</a>: bool):
    output = {"item_id": item_id, "short": short}
    return output
</code></pre>

When you visit `http://127.0.0.1:8000/items/foo`, you will encounter the following error due to the missing `short` parameter in the URL.

```json
{
    "detail": [
        {
            "type": "missing",
            "loc": [
                "query",
                "short"
            ],
            "msg": "Field required",
            "input": null,
            "url": "https://errors.pydantic.dev/2.5/v/missing"
        }
    ]
}
```

## API Docs

Once again, FastAPI can generate API Docs in both Swagger and ReDoc which are available at the following URLs:

* [http://127.0.0.1:8000/docs/](http://127.0.0.1:8000/docs#/)
* <http://127.0.0.1:8000/redoc>

***

## Summary

Query parameters in FastAPI are a powerful and flexible way to add functionality to your API endpoints. By leveraging Python type hints and FastAPI's intuitive design, developers can easily define, validate, and document query parameters, resulting in robust, efficient, and easy-to-use APIs.

***

## Exercises

**1. Filtering Numbers**

**Task:** Create an endpoint `/filter` that accepts two query parameters `min` and `max` (both integers) and returns a list of numbers between them (inclusive).

**Example:**

* Request: `GET /filter?min=3&max=7`
* Response:

```json
{
  "numbers": [3, 4, 5, 6, 7]
}
```

***

**2. Search Products**

**Task:** Given an in-memory product list:

```python
products = [
    {"id": 1, "name": "Laptop", "price": 1200},
    {"id": 2, "name": "Phone", "price": 800},
    {"id": 3, "name": "Tablet", "price": 500}
]
```

Create an endpoint `/search` that accepts an optional query parameter `keyword`.

* If `keyword` is provided, return only products whose name contains the keyword (case-insensitive).
* If no keyword is provided, return all products.

**Example:**

* Request: `GET /search?keyword=lap`
* Response:

```json
[
  {"id": 1, "name": "Laptop", "price": 1200}
]
```

* Request: `GET /search`
* Response:

```json
[
  {"id": 1, "name": "Laptop", "price": 1200},
  {"id": 2, "name": "Phone", "price": 800},
  {"id": 3, "name": "Tablet", "price": 500}
]
```

***

**3. Product Discount Calculation**

**Task:** Given the following product list:

```python
products = {
    1: {"name": "Laptop", "price": 1200},
    2: {"name": "Phone", "price": 800},
    3: {"name": "Tablet", "price": 500}
}
```

Create an endpoint `/products/{product_id}` that also accepts an optional query parameter `discount` (integer, default = 0).

* If `discount` is provided, return the product price after applying the discount percentage.
* If `discount` is missing, return the original product price.

**Example:**

* Request: `GET /products/1?discount=10`
* Response:

```json
{
  "id": 1,
  "name": "Laptop",
  "original_price": 1200,
  "discount": 10,
  "final_price": 1080
}
```

***

**4. Paginated Blog Posts by User**

**Task:** You have an in-memory list of blog posts stored per user. Create an endpoint `/users/{user_id}/posts` that accepts two query parameters:

* `limit` (default = 5): maximum number of posts to return.
* `offset` (default = 0): number of posts to skip before returning results.

**Example:**

* Request: `GET /users/3/posts?limit=2&offset=1`
* Response:

```json
{
  "user_id": 3,
  "posts": [
    {"id": 2, "title": "Second Post"},
    {"id": 3, "title": "Third Post"}
  ]
}
```

***

**5. Flight Search by Route and Filters**

**Task:** Design an endpoint `/flights/{origin}/{destination}` that accepts optional query parameters:

* `date` (string, format: `YYYY-MM-DD`)
* `max_price` (integer, optional)

Return a list of available flights filtered by origin, destination, date, and price if provided.

**Example:**

* Request: `GET /flights/NYC/LAX?date=2025-09-15&max_price=400`
* Response:

{% code overflow="wrap" %}

```json
[
    {
        "flight_id": 101,
        "origin": "NYC",
        "destination": "LAX",
        "date": "2025-09-15",
        "price": 350
    }
]
```

{% endcode %}

***

**6. Movie Ticket Booking System**

**Scenario:** You are building an API for an online movie booking system.

**Requirements:**

1. Create an endpoint:

```
/cinemas/{cinema_id}/movies/{movie_id}/showtimes
```

* `cinema_id` (int) → identifies the cinema.
* `movie_id` (int) → identifies the movie.

2. Accept the following **query parameters**:
   * `date` (string, format: `YYYY-MM-DD`, required) → the date of the show.
   * `limit` (int, optional, default = 5) → maximum number of showtimes to return.
3. The endpoint should return a JSON object with available showtimes for that movie in that cinema, filtered by the given date and limited by the `limit` query parameter.

**Example Request:**

```
GET /cinemas/2/movies/10/showtimes?date=2025-09-14&limit=2
```

**Example Response:**

```json
{
  "cinema_id": 2,
  "movie_id": 10,
  "date": "2025-09-14",
  "showtimes": [
    {"time": "14:00", "available_seats": 45},
    {"time": "17:30", "available_seats": 20}
  ]
}
```

***

[^1]: Path parameter

[^2]: Query parameter
