Request & Response Patterns
Pydantic request/response models, validation before the application layer, and domain-to-response mapping.
Status: Extracted
Purpose: Pydantic request/response models, validation patterns, and API contracts.
Request/Response Objects
Pydantic Models
Standard: All request objects use Pydantic for validation
Benefits:
- Automatic validation
- Type safety
- Clear API contracts
- OpenAPI documentation generation
Pattern:
# api/schemas/account.py
from pydantic import BaseModel, Field
class CreateAccountRequest(BaseModel):
"""Request to create account."""
phone: str = Field(..., min_length=1, description="Phone number")
name: str = Field(..., min_length=1, description="Account name")
# ... other fields
class AccountResponse(BaseModel):
"""Account response."""
id: str
phone: str
name: str
created_at: datetime
# ... other fields
@classmethod
def from_domain(cls, account: Account) -> "AccountResponse":
"""Create response from domain entity."""
return cls(
id=account.id,
phone=account.phone,
name=account.name,
created_at=account.created_at,
)Request Validation
Validation Before Application Layer
Pattern: Request objects validated before reaching application layer
Flow:
- FastAPI validates request against Pydantic model
- Validation errors returned as 400 Bad Request
- Validated request passed to route handler
- Route handler converts to command/query
Example:
@router.post("/")
async def create_account(
request: CreateAccountRequest, # Validated by FastAPI
uow: IUnitOfWork = Depends(get_unit_of_work),
) -> AccountResponse:
"""Create account."""
# Request already validated
command = CreateAccountCommand(
phone=request.phone,
name=request.name,
)
account = await handle_create_account(command, uow)
return AccountResponse.from_domain(account)Response Patterns
Response Models
Pattern: Separate response models for different operations
Examples:
AccountResponse- Single account responseAccountListResponse- List of accountsCreateAccountResponse- Create operation responseUpdateAccountResponse- Update operation response
Pattern:
class AccountResponse(BaseModel):
"""Account response."""
id: str
phone: str
name: str
created_at: datetime
class AccountListResponse(BaseModel):
"""List of accounts response."""
accounts: list[AccountResponse]
total: int
limit: int
offset: intDomain-to-Response Mapping
Pattern: Convert domain entities to response models
Method:
class AccountResponse(BaseModel):
"""Account response."""
# ... fields
@classmethod
def from_domain(cls, account: Account) -> "AccountResponse":
"""Create response from domain entity."""
return cls(
id=account.id,
phone=account.phone,
name=account.name,
created_at=account.created_at,
)Operation-Specific Schemas
Create vs Update Schemas
Pattern: Separate schemas for CREATE and UPDATE operations
Rationale:
- CREATE operations: New entities don't have IDs yet
- UPDATE operations: Existing entities may include IDs
- Different validation rules for create vs update
Example:
# Create schema - no ID required
class CreateAccountRequest(BaseModel):
phone: str
name: str
# No id field
# Update schema - ID required
class UpdateAccountRequest(BaseModel):
id: str # Required for update
phone: str | None = None # Optional fields
name: str | None = NoneAPI Contracts
Contract Definition
Request and response objects together form the API contracts
Components:
- Request models (Pydantic)
- Response models (Pydantic)
- Endpoint definitions (FastAPI routes)
- Error responses (exception handling)
Documentation:
- OpenAPI/Swagger documentation auto-generated
- Request/response examples
- Validation rules documented
- Error responses documented
Related Documents
- REST API Patterns - Route organization
- Error Response Formats - Error handling
- API Validation Schemas - Frontend validation
Reference implementation: api/schemas/ in any FastAPI service.