[FastAPI Series 3] Separate Path and Query Parameters

한국어 버전

While building APIs you must distinguish "values that identify the resource" from "optional filters." Path parameters are embedded in the URL such as /items/10; they are required and specify which entity you want. Query parameters appear as ?sort=latest and add options such as sorting, search keywords, or pagination. A quick mental model: GET /items/10?highlight=price fetches item 10 (path) and optionally toggles how it should be displayed (query). Separating them keeps URLs readable and lets FastAPI mark required fields automatically. Using the same server from part 2, this post demonstrates how to declare and verify both kinds of parameters.

Key terms

  1. Path parameter – the required piece inside the URL pattern /items/{id}; this post starts with it.
  2. Query parameter – optional ?key=value pairs for sorting or filtering.
  3. 422 response – FastAPI’s automatic "validation failed" status when values are missing or typed incorrectly.

Practice card

  • Estimated time: 35 minutes
  • Prereqs: FastAPI starter app from part 2
  • Goal: declare path and query parameters and inspect them in Swagger UI

What this post covers

  • Declare path parameters and watch FastAPI convert types
  • Mark query parameters as required or optional
  • Practice with uv run fastapi dev main.py and compare both in Swagger UI

Understanding path parameters: defining the resource

Path parameters live inside the URL structure and are required.

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: int):
    return {"item_id": item_id}

Type hints such as int trigger automatic conversion; passing a non-number yields a 422 explaining the error (the response body will point to {"loc": ["path", "item_id"], "msg": "Input should be a valid integer"}). Make sure the variable in the decorator ({item_id}) matches the function argument name; FastAPI raises an error if they differ.

Understanding query parameters: adding options

Query parameters appear as ?key=value and typically control filters or pagination.

@app.get("/items")
def list_items(q: str | None = None, limit: int = 10):
    base_items = ["pencil", "pen", "notebook", "eraser"]
    filtered = [item for item in base_items if not q or q.lower() in item.lower()]
    return filtered[:limit]
  • Setting q’s default to None makes it optional.
  • Giving limit a default keeps responses capped at 10 unless specified.
  • Remove the default entirely (e.g., q: str) to make a query parameter required—the moment you provide a default, FastAPI treats it as optional in both code and documentation.

Python version tip: the str | None syntax requires Python 3.10+. If you are on 3.9 or earlier, use Optional[str] = None from the typing module.

FastAPI interprets any function argument that does not appear in the path template as a query parameter unless you wrap it in Body, Header, or similar helpers.

Use a path parameter for the team name and a query parameter for sorting.

@app.get("/teams/{team_name}")
def get_team(team_name: str, sort: str = "latest"):
    data = fetch_team(team_name)  # pretend function that returns {"members": [...]}
    order_key = "created_at" if sort == "latest" else "score"
    return sorted(data["members"], key=lambda member: member[order_key], reverse=True)

Requests like GET /teams/racing?sort=score immediately reveal what is required and what is optional; Swagger UI shows the same distinction.

Spot the difference in Swagger UI

Run the server:

uv run fastapi dev main.py

Visit http://127.0.0.1:8000/docs in your browser. GET /items/{item_id} displays a Path Parameters section while GET /items renders a Query Params section. The UI alone teaches where each value belongs.

Command fallback: if fastapi dev is unavailable, run uv run uvicorn main:app --reload instead; both commands expose the same /docs page.

Add validation rules

Use helpers such as Query to enforce rules.

from fastapi import Query

@app.get("/search")
def search_items(keyword: str = Query(min_length=2, max_length=20)):
    return {"keyword": keyword}

Swagger surfaces those constraints automatically, and invalid input returns a 422 detailing the issue.

Recap

  • Path parameters are mandatory pieces of the URL pattern and must use the same name in the decorator and function.
  • Query parameters come after ? and become optional as soon as you provide a default value.
  • Type hints and helpers like Query both validate input and document it.

Why the separation matters

Clear roles mean clients know which values are required and which are optional. URLs stay shareable and cacheable, and repeating the same request becomes easier because the critical parts stay in the path.

Security reminder: everything in the path or query string is logged and visible in browser history, so avoid sending secrets or passwords as parameters—use headers or encrypted bodies instead.

Practice

  • Follow along: implement /items/{item_id} and /items, then call each route in Swagger UI.
  • Extend: add GET /search with min_length and confirm the 422 when only one character is provided.
  • Debug: intentionally send the wrong type and explain the 422 payload.
  • Done when: you can verbally describe the difference between path/query params and both endpoints return 200/422 as expected.
  • Bonus: repeat the same requests with curl or httpx to reinforce how to build URLs manually.

Wrap-up

Distinguishing path and query parameters instantly clarifies your API design. Next we will accept JSON request bodies and validate them with Pydantic models.

💬 댓글

이 글에 대한 의견을 남겨주세요