Folder Structure
🌳 C:\Users\soura\Documents\mine\fastapi\blog
├── 📁 alembic
| ├── 📄 env.py
| ├── 📄 README
| ├── 📄 script.py.mako
| └── 📁 versions
├── 📄 alembic.ini
├── 📁 core
| └── 📄 config.py
├── 📁 database
| ├── 📄 db.py
| └── 📁 models
├── 📄 docker-compose.yaml
├── 📄 Dockerfile
├── 📄 main.py
├── 📄 requirements.txt
└── 📁 schemas #new folder
└── 📄 user.py
Pydantic Schemas - The Data Validators
Let us create an API for user registration. Before that we will need to make sure, If someone is filing our signup form, They are sending valid data. A user could literally send {"email": "not-an-email", "password": "1"}
and our system would happily accept it.
What are Schemas Anyway ?
Think of schemas as your API's bouncer. Just like a bouncer at a club checks IDs and dress codes, schemas check if the incoming data meets your requirements before letting it into your application. No valid email? Sorry, you're not getting in. Password too short? Nope, try again.
Pydantic makes this super easy and elegant. It's like having a really smart bouncer who can tell you exactly what's wrong with the data and how to fix it.
Let's Build Our User Schema
Now, let's create our user schema in schemas/user.py:
from pydantic import BaseModel, Field, field_validator
class UserCreate(BaseModel):
full_name: str = Field(..., min_length=2, max_length=255)
email: str = Field(..., min_length=5, max_length=255)
password: str = Field(..., min_length=4, max_length=50)
@field_validator('email')
@classmethod
def validate_email(cls, v):
if '@' not in v:
raise ValueError('Email must contain @')
return v
Let me explain what's happening here:
-
BaseModel: This is the superhero parent class from Pydantic. When our class inherits from it, it gets superpowers like automatic validation, serialization, and error handling.
-
Field(..., min_length=4): This is where it gets interesting. The
...
means this field is required (you can't skip it), andmin_length=4
ensures the password has at least 4 characters.
Why Schemas Are our Best Friend ?
Let me tell you why schemas are important:
Without using Schemas
@app.post("/register/")
def register_user(data: dict):
email = data.get("email") # Could be None, could be "banana"
password = data.get("password") # Could be "a"
# Now I have to manually check everything
if not email or "@" not in email:
return {"error": "Invalid email"}
if not password or len(password) < 4:
return {"error": "Password too short"}
# More manual validation hell...
With Schemas:
@app.post("/register/")
def register_user(user_data: UserCreate):
print(user_data.email) # Clean, validated email
print(user_data.password) # Clean, validated password
See the difference? With schemas, FastAPI automatically validates the data before it even reaches your function. If validation fails, it returns a proper HTTP error response with details about what went wrong.
Let's experiment a bit with Pydantic schema and try it out in a hands on manner:
PS C:\Users\soura\Documents\mine\fastapi\blog> docker-compose exec -it web /bin/bash
root@119e377fa1c5:/app# python
Python 3.13.5
>>> from schemas.user import UserCreate
>>> UserCreate(full_name="John Wicked", email="[email protected]", password="Test@123")
>>> UserCreate(full_name="John", email="fastapi",password="Test@123")
Traceback (most recent call last):
Value error, Email must contain @
>>>
This is what I wanted to demostrate, In case the input or output data from API response does not respect schema. Our APIs will be throwing meaningful and beautiful error. In the terminal you are not experiencing it to the fullest. But in the API endpoint it will be super useful. This is exactly what we are going to be working in the next tutorial.