3. Request bodies¶
To create a task the client sends JSON. Declare a Pydantic model and annotate a handler parameter with it: Veloce reads the request body, validates it against the model, and hands your handler a fully-typed object.
Update tasks.py to add a Task model and a POST handler:
from pydantic import BaseModel
from veloce import Veloce
app = Veloce(title="Tasks API", version="1.0.0")
# Still a throwaway store — we wire in dependency injection next step.
_tasks: list[dict] = []
class TaskCreate(BaseModel):
title: str
done: bool = False
@app.get("/tasks")
async def list_tasks():
return _tasks
@app.post("/tasks")
async def create_task(task: TaskCreate):
record = {"id": len(_tasks) + 1, **task.model_dump()}
_tasks.append(record)
return record, 201
if __name__ == "__main__":
app.run(port=8000)
Run it and create a task:
curl -X POST localhost:8000/tasks \
-H "Content-Type: application/json" \
-d '{"title": "write the tutorial"}'
You get back the created record with a 201 Created status:
What just happened¶
TaskCreateis an ordinary PydanticBaseModel. Because thetaskparameter is annotated with it, Veloce reads the JSON body and validates it.done: bool = Falseis optional with a default, so the client may omit it.task.model_dump()turns the validated model back into a plain dict to store.- Returning
record, 201is the (body, status) tuple shorthand — Veloce serialisesrecordas JSON and sets the status to201. A barereturn recordwould default to200.
Validation comes for free¶
The model defines the contract, so bad input is rejected before your code runs. Send a body with the wrong type:
curl -X POST localhost:8000/tasks \
-H "Content-Type: application/json" \
-d '{"title": 123, "done": "maybe"}'
Veloce responds with a 422 and a structured error body pointing at each bad
field — you never wrote a single if:
A missing required field (title) is rejected the same way.
Separate input and output models
A common pattern is one model for the incoming body (TaskCreate) and
another for the response, so you never accidentally echo internal
fields. Declare the response shape with response_model= on the route when
you want Veloce to enforce and document it.
Next steps¶
The _tasks list is a module global — fine for one file, awkward to test or
swap. Next we inject it as a dependency:
Dependencies.