Generate FastAPI endpoints from a model

2025-11-05

In this post, we’ll see how to turn a Pydantic model class into an endpoint where each filed is an endpoint. The reason for why you’d to do this are irrelevant but it is an intersting experiment.

First let’s look at the simplest FastAPI app:

from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
def hello():
    return {"hello": "world"}

When you’ve installed FastAPI, you can run this with just fastapi dev.

A simple curl localhost:8000/hello will show that it works, but if you’re a visual, you can have a look at the docs that are automatically generated: http://localhost:8000/docs.

Here is a screenshot:

Screenshot of FastAPI docs

Now that you have the basic thing running, we can make things interesting. First, let’s define a somewhat complex model:

class Address(BaseModel):
    street: str
    city: str

class Person(BaseModel):
    name: str
    age: int
    address: Address

And use it to return from the /person endpoint, the docs are updated nicely:

person_model = Person(name="name", age=18, address=Address(street="street", city="city"))

@app.get("/person")
def person() -> Person:
    return person_model

Screenshot of FastAPI docs

Now let’s use this Person type to generate an endpoint for each field of the class. Well, we’ll do two, one for fetching the value of a field, and the other one for updating it.

To achieve this, we’ll iterate over the model_fields member coming from Pydantic:

for field_name, field_type in Person.model_fields.items():

    @app.get(f"/person/{field_name}")
    def get_field(field_name: str = field_name): # 1
        return person_model.model_dump()[field_name]

    @app.put(f"/person/{field_name}")
    def put_field( # 2
        value: field_type.annotation,  # pyright: ignore[reportInvalidTypeForm]
        field_name: str = field_name,
    ):
        Person.model_validate( # 3
            {
                **person_model.model_dump(),
                field_name: value,
            }
        )
        return {"message": "Configuration updated successfully"}

The code is nothing fancy, but let’s go over some details that tripped me the first time I implement this:

  1. because we’re iterating over a loop and creating functions inside the loop, we must be careful when using loop variables inside the functions/lambas we create. I got lucky that I had ruff installed so it told me before I had completed my implementation
  2. the write side is the more interesting because we want the input type to be properly typed and documented. To do this, we use the FieldInfo#annotation function that returns the type for the given field. The only downside I have found so far is that I can’t get pyright or any type checker I’ve tried to type check this
  3. we can use Pydantic useful model_validate to ensure at runtime that the input received is valid within the larger model

Going back to 2, the great thing about it is that it allows FastAPI to correctly annotate everything in the documentation endpoint it builds for you. For example:

Screenshot of FastAPI docs

And that’s it. If you wanted, you could generate such endpoints under another endpoint through a FastAPI APIRouter instead of the app directly.