Python FastAPI Tutorial (Part 4): Pydantic Schemas - Request and Response Validation
Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.
Create a dedicated schemas.py file to define Pydantic models that act as the API contract for both requests and responses.
Briefing
FastAPI’s request/response validation becomes concrete once Pydantic schemas define the API contract—what clients must send and what the server will return. By adding three Pydantic models (shared fields, create payload, and response payload) and wiring them into endpoint decorators, the API gains runtime enforcement of field constraints, automatic 422 error responses for bad input, and documentation that lists exact field types and validation rules.
The tutorial starts by addressing a gap in the existing endpoints: they return plain dictionaries with no schema, so the interactive docs don’t show response structure, and there’s no safe way to create posts through the API. Pydantic is introduced as the validation layer built into FastAPI (installed automatically as a dependency). Unlike “type hints as documentation only,” Pydantic uses those type hints at runtime to validate incoming data and generate detailed error messages when data doesn’t match.
A new schemas.py file is created to centralize the contract. A PostBase model inherits from BaseModel and defines shared fields for title, content, and author. Field constraints are added using Field—title requires a minimum length of 1 and a maximum length of 100 characters; content requires at least 1 character; author requires at least 1 and at most 50 characters. Separate models then clarify intent: PostCreate inherits from PostBase to represent what clients send when creating a post, while PostResponse inherits from PostBase and adds server-generated fields like id (integer) and date_posted (string for now). PostResponse also sets model configuration with config dict using from_attributes=True, which prepares the schemas to read data from attribute-based objects (important once a database layer replaces in-memory dictionaries).
With schemas in place, main.py is updated to import PostCreate and PostResponse. The GET endpoints gain response_model annotations: the “list posts” route returns a list of PostResponse objects, and the “single post” route returns one PostResponse. These response models do more than improve docs—they validate outgoing data and filter out extra fields, helping prevent accidental data leakage.
A new POST /posts endpoint is added with response_model=PostResponse and status_code=201. FastAPI uses the PostCreate type hint to parse and validate the JSON body before the endpoint function runs. If validation fails, the client receives a 422 response with specific details. When validation succeeds, the handler generates an id, constructs a post dictionary from the validated fields, appends it to the in-memory list, and returns the new post.
Testing in the interactive docs confirms the behavior: successful creation returns a 201 with id and date_posted; missing required fields triggers 422 errors; and constraint violations (like too-short strings) produce precise messages. Finally, the tutorial highlights a practical limitation: created posts vanish after a server restart because they’re stored in a Python list in memory. Persistent storage requires a database, which is slated for the next tutorial.
Overall, the key takeaway is that Pydantic schemas define the API contract and enable FastAPI to handle validation, serialization, and documentation consistently—before introducing database-backed persistence and relationships.
Cornell Notes
Pydantic schemas turn FastAPI endpoints into a strict contract: clients know exactly what to send, and the server guarantees what it returns. The tutorial builds three models—PostBase (shared fields with constraints), PostCreate (what clients submit), and PostResponse (what the API returns, including server-generated id and date_posted). By attaching response_model to GET routes and using PostCreate as the request body type for POST, FastAPI validates inputs and outputs at runtime and returns detailed 422 errors when data is invalid. The interactive docs automatically display field types and constraints, making the API self-describing. The in-memory list used for posts is only temporary, so data disappears after restart, motivating the next step: adding a database.
How do PostBase, PostCreate, and PostResponse differ, and why split them into separate classes?
What does response_model change for GET endpoints beyond documentation?
How does FastAPI produce a 422 validation error before endpoint code runs?
Why set from_attributes=True in the PostResponse model configuration?
Why do newly created posts disappear after restarting the server?
Review Questions
- What specific constraints are applied to title and author in PostBase, and how do those constraints surface to clients when validation fails?
- How do response_model annotations on GET routes affect both the interactive docs and the actual runtime behavior of returned data?
- What role does from_attributes=True play, and what change in the data layer makes it necessary?
Key Points
- 1
Create a dedicated schemas.py file to define Pydantic models that act as the API contract for both requests and responses.
- 2
Use Field constraints (min_length/max_length) in PostBase so invalid inputs trigger automatic 422 errors with detailed messages.
- 3
Separate PostCreate (client input) from PostResponse (server output) to keep request/response shapes clear and extensible.
- 4
Attach response_model to GET endpoints so FastAPI validates outgoing data, filters extra fields, and generates precise schema documentation.
- 5
Use PostCreate as the request body type for POST endpoints so FastAPI validates JSON before endpoint logic runs.
- 6
Set from_attributes=True in response models to support attribute-based ORM objects once a database replaces in-memory dictionaries.
- 7
Recognize that in-memory storage loses data on restart, so persistence requires a database in the next step.