Skip to content

    Chapter 4, Basic

    Path parameters

    A route that changes according to a piece of the URL: product 12, user 7. A path parameter avoids creating a route for each value.

    A dynamic piece of the URL

    Between braces, the snippet becomes a variable that arrives at the function with the same name. Without a type, it arrives as text. With a type, FastAPI converts and validates:

    basico/cap04_parametros_caminho.pylinhas 10 a 22
    from fastapi import FastAPI
    from fastapi.testclient import TestClient
    
    app = FastAPI()
    
    
    @app.get("/usuarios/{usuario_id}")
    def obter_usuario(usuario_id: int):
        return {"usuario_id": usuario_id, "tipo": type(usuario_id).__name__}
    
    
    cliente = TestClient(app)
    print(cliente.get("/usuarios/200").json())
    
    Output
    {'usuario_id': 200, 'tipo': 'int'}
    

    The "200" from the URL arrived at the function as the integer 200. When the value is not of the requested type, the request is rejected before the function runs, with a 422 that states what went wrong:

    basico/cap04_parametros_caminho.pylinhas 24 a 28
    resposta = cliente.get("/usuarios/abc")
    erro = resposta.json()["detail"][0]
    print(resposta.status_code)
    print(erro["loc"], erro["type"])
    print(erro["msg"])
    
    Output
    422
    ['path', 'usuario_id'] int_parsing
    Input should be a valid integer, unable to parse string as an integer
    

    The field loc indicates where the problem is (the path, the parameter usuario_id), the type classifies the error, and the msg explains. No line of your code was written for this.

    Restrictions with `Path`

    To impose rules beyond the type (greater than zero, at most so many), use Path along with Annotated. The restrictions also appear in the documentation:

    basico/cap04_parametros_caminho.pylinhas 33 a 46
    from typing import Annotated
    
    from fastapi import Path
    
    
    @app.get("/produtos/{produto_id}")
    def obter_produto(
        produto_id: Annotated[int, Path(gt=0, description="Identificador do produto")],
    ):
        return {"produto_id": produto_id}
    
    
    print(cliente.get("/produtos/5").status_code, cliente.get("/produtos/0").status_code)
    print(cliente.get("/produtos/0").json()["detail"][0]["msg"])
    
    Output
    200 422
    Input should be greater than 0
    

    The order of routes matters

    FastAPI evaluates the routes in the order they were declared, and the first one that matches wins. A fixed route like /usuarios/eu needs to come before from the dynamic /usuarios/{usuario_id}, otherwise the text eu is taken as the value of the parameter:

    basico/cap04_parametros_caminho.pylinhas 51 a 79
    app_errado = FastAPI()
    
    
    @app_errado.get("/pessoas/{pessoa_id}")
    def pessoa(pessoa_id: int):
        return {"id": pessoa_id}
    
    
    @app_errado.get("/pessoas/eu")
    def eu_errado():
        return {"quem": "eu"}
    
    
    print("dinâmica primeiro:", TestClient(app_errado).get("/pessoas/eu").status_code)
    
    app_certo = FastAPI()
    
    
    @app_certo.get("/pessoas/eu")
    def eu_certo():
        return {"quem": "eu"}
    
    
    @app_certo.get("/pessoas/{pessoa_id}")
    def pessoa_certa(pessoa_id: int):
        return {"id": pessoa_id}
    
    
    print("fixa primeiro:", TestClient(app_certo).get("/pessoas/eu").json())
    
    Output
    dinâmica primeiro: 422
    fixa primeiro: {'quem': 'eu'}
    

    Values from a closed list

    When only some values are accepted, use a Enum. The validation and documentation will list the options:

    basico/cap04_parametros_caminho.pylinhas 84 a 99
    from enum import Enum
    
    
    class Categoria(str, Enum):
        livros = "livros"
        jogos = "jogos"
    
    
    @app.get("/catalogo/{categoria}")
    def catalogo(categoria: Categoria):
        return {"categoria": categoria.value}
    
    
    print(cliente.get("/catalogo/jogos").json())
    resposta = cliente.get("/catalogo/roupas")
    print(resposta.status_code, resposta.json()["detail"][0]["type"])
    
    Output
    {'categoria': 'jogos'}
    422 enum
    

    Paths with slashes

    A normal path parameter does not accept /. When the value is a file path, the converter :path captures the entire rest:

    basico/cap04_parametros_caminho.pylinhas 104 a 109
    @app.get("/arquivos/{caminho:path}")
    def arquivo(caminho: str):
        return {"caminho": caminho}
    
    
    print(cliente.get("/arquivos/docs/2026/relatorio.pdf").json())
    
    Output
    {'caminho': 'docs/2026/relatorio.pdf'}
    

    Path parameter identifies a resource

    I use the path to identify something (user 7, request 123), and the query string (next chapter) to filter or adjust (?ativo=true). If the URL without that value wouldn't make sense, the value belongs to the path.

    Exercise 1

    A product, validated

    Create GET /itens/{item_id} with item_id integer greater than zero. Check that 5 returns 200, that 0 and abc return 422.