Path and query parameters: type-hinted arguments#
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 butidin 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 thoughbool | Nonereads 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 realintbefore 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=truebecomes acompleted: boolparameter, with norequest.args.get("completed")call anywhere. - Give a query parameter a default value to make it optional.
completed: bool | None = Noneis the exactstr | Nonesyntax 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-numberagainstnote_id: intfails with a422beforeget_noteever 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]