Path and query parameters: type-hinted arguments#

← Back to FastAPI

Path vs query, and how FastAPI tells them apart#

FastAPI decides whether a function parameter is a path parameter or a query parameter by one rule only: does its name appear inside {} in the decorated path string. note_id in @app.get("/notes/{note_id}") is named right there in the path, so it's a path parameter, required, and always part of the URL itself. completed in @app.get("/notes") isn't mentioned in the path anywhere, so FastAPI reads it from the query string instead, ?completed=true in the actual request URL. There's no separate object to reach for, the way Flask's request.args was a distinct thing from the URL rule itself; it's one function signature, and the path string alone decides which parameters come from where.

Optional means a default, not a | None on its own#

completed: bool | None = None is doing two things, not one. bool | None says the value, once you have it, is either a real boolean or nothing. = None is what actually makes the parameter optional; without a default, FastAPI treats every parameter as required, str | None or not, and rejects a request that leaves it out. This is the same rule Week 1's mutable-default note already covered for function parameters generally, just applied here to a request's query string instead of a plain function call.

What to watch for#

  • Misspelling a path parameter's name between the decorator string and the function signature, {note_id} in the path but id in the function. FastAPI matches them by name, not by position, so a mismatch there is a real bug, not a style nit; you'll likely see an error about a missing required argument rather than anything obviously pointing at the typo.
  • Forgetting the default on an optional query parameter. Leave off = None (or any other default) and FastAPI treats it as required, rejecting any request that doesn't include it, even though bool | None reads like it should already be optional on its own.
  • Assuming query parameters arrive as strings by default, the way Flask's request.args.get(...) always handed back a string regardless of what you actually wanted. FastAPI converts to whatever type you hint, bool, int, or otherwise, the same conversion path parameters get.

TL;DR#

Both a URL's path segments and its query string are read the same way now, as ordinary, type-hinted parameters on your function, no <int:note_id> converter syntax and no request.args.get(...) calls.

  • A {note_id} placeholder in the decorated path becomes a same-named function parameter. Type-hint it (note_id: int) and FastAPI converts the URL's text into a real int before your function runs, the same job Flask's <int:note_id> converter did.
  • Any function parameter that isn't part of the path string is read from the query string instead, automatically. GET /notes?completed=true becomes a completed: bool parameter, with no request.args.get("completed") call anywhere.
  • Give a query parameter a default value to make it optional. completed: bool | None = None is the exact str | None syntax from this module's note on type hints, now describing a query parameter instead of a plain variable.
  • These get the same automatic validation as a request body. A request for /notes/not-a-number against note_id: int fails with a 422 before get_note ever runs, instead of Flask's <int:...> converter quietly 404ing on a type mismatch.
from fastapi import FastAPI

app = FastAPI()


@app.get("/notes/{note_id}")
def get_note(note_id: int):
    return find_note(note_id)


@app.get("/notes")
def list_notes(completed: bool | None = None):
    if completed is None:
        return notes
    return [note for note in notes if note.get("completed") == completed]