235 lines
6.3 KiB
Markdown
235 lines
6.3 KiB
Markdown
# Database Layer
|
|
|
|
Learn how to work with the database layer in the FastAPI Boilerplate. This section covers everything you need to store and retrieve data effectively.
|
|
|
|
## What You'll Learn
|
|
|
|
- **[Models](models.md)** - Define database tables with SQLAlchemy models
|
|
- **[Schemas](schemas.md)** - Validate and serialize data with Pydantic schemas
|
|
- **[CRUD Operations](crud.md)** - Perform database operations with FastCRUD
|
|
- **[Migrations](migrations.md)** - Manage database schema changes with Alembic
|
|
|
|
## Quick Overview
|
|
|
|
The boilerplate uses a layered architecture that separates concerns:
|
|
|
|
```python
|
|
# API Endpoint
|
|
@router.post("/", response_model=UserRead)
|
|
async def create_user(user_data: UserCreate, db: AsyncSession):
|
|
return await crud_users.create(db=db, object=user_data)
|
|
|
|
# The layers work together:
|
|
# 1. UserCreate schema validates the input
|
|
# 2. crud_users handles the database operation
|
|
# 3. User model defines the database table
|
|
# 4. UserRead schema formats the response
|
|
```
|
|
|
|
## Architecture
|
|
|
|
The database layer follows a clear separation:
|
|
|
|
```
|
|
API Request
|
|
↓
|
|
Pydantic Schema (validation & serialization)
|
|
↓
|
|
CRUD Layer (business logic & database operations)
|
|
↓
|
|
SQLAlchemy Model (database table definition)
|
|
↓
|
|
PostgreSQL Database
|
|
```
|
|
|
|
## Key Features
|
|
|
|
### 🗄️ **SQLAlchemy 2.0 Models**
|
|
Modern async SQLAlchemy with type hints:
|
|
```python
|
|
class User(Base):
|
|
__tablename__ = "user"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
username: Mapped[str] = mapped_column(String(50), unique=True)
|
|
email: Mapped[str] = mapped_column(String(100), unique=True)
|
|
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
|
```
|
|
|
|
### ✅ **Pydantic Schemas**
|
|
Automatic validation and serialization:
|
|
```python
|
|
class UserCreate(BaseModel):
|
|
username: str = Field(min_length=2, max_length=50)
|
|
email: EmailStr
|
|
password: str = Field(min_length=8)
|
|
|
|
class UserRead(BaseModel):
|
|
id: int
|
|
username: str
|
|
email: str
|
|
created_at: datetime
|
|
# Note: no password field in read schema
|
|
```
|
|
|
|
### 🔧 **FastCRUD Operations**
|
|
Consistent database operations:
|
|
```python
|
|
# Create
|
|
user = await crud_users.create(db=db, object=user_create)
|
|
|
|
# Read
|
|
user = await crud_users.get(db=db, id=user_id)
|
|
users = await crud_users.get_multi(db=db, offset=0, limit=10)
|
|
|
|
# Update
|
|
user = await crud_users.update(db=db, object=user_update, id=user_id)
|
|
|
|
# Delete (soft delete)
|
|
await crud_users.delete(db=db, id=user_id)
|
|
```
|
|
|
|
### 🔄 **Database Migrations**
|
|
Track schema changes with Alembic:
|
|
```bash
|
|
# Generate migration
|
|
alembic revision --autogenerate -m "Add user table"
|
|
|
|
# Apply migrations
|
|
alembic upgrade head
|
|
|
|
# Rollback if needed
|
|
alembic downgrade -1
|
|
```
|
|
|
|
## Database Setup
|
|
|
|
The boilerplate is configured for PostgreSQL with async support:
|
|
|
|
### Environment Configuration
|
|
```bash
|
|
# .env file
|
|
POSTGRES_USER=your_user
|
|
POSTGRES_PASSWORD=your_password
|
|
POSTGRES_SERVER=localhost
|
|
POSTGRES_PORT=5432
|
|
POSTGRES_DB=your_database
|
|
```
|
|
|
|
### Connection Management
|
|
```python
|
|
# Database session dependency
|
|
async def async_get_db() -> AsyncIterator[AsyncSession]:
|
|
async with async_session_maker() as session:
|
|
yield session
|
|
|
|
# Use in endpoints
|
|
@router.get("/users/")
|
|
async def get_users(db: Annotated[AsyncSession, Depends(async_get_db)]):
|
|
return await crud_users.get_multi(db=db)
|
|
```
|
|
|
|
## Included Models
|
|
|
|
The boilerplate includes four example models:
|
|
|
|
### **User Model** - Authentication & user management
|
|
- Username, email, password (hashed)
|
|
- Soft delete support
|
|
- Tier-based access control
|
|
|
|
### **Post Model** - Content with user relationships
|
|
- Title, content, creation metadata
|
|
- Foreign key to user (no SQLAlchemy relationships)
|
|
- Soft delete built-in
|
|
|
|
### **Tier Model** - User subscription levels
|
|
- Name-based tiers (free, premium, etc.)
|
|
- Links to rate limiting system
|
|
|
|
### **Rate Limit Model** - API access control
|
|
- Path-specific rate limits per tier
|
|
- Configurable limits and time periods
|
|
|
|
## Directory Structure
|
|
|
|
```text
|
|
src/app/
|
|
├── models/ # SQLAlchemy models (database tables)
|
|
│ ├── __init__.py
|
|
│ ├── user.py # User table definition
|
|
│ ├── post.py # Post table definition
|
|
│ └── ...
|
|
├── schemas/ # Pydantic schemas (validation)
|
|
│ ├── __init__.py
|
|
│ ├── user.py # User validation schemas
|
|
│ ├── post.py # Post validation schemas
|
|
│ └── ...
|
|
├── crud/ # Database operations
|
|
│ ├── __init__.py
|
|
│ ├── crud_users.py # User CRUD operations
|
|
│ ├── crud_posts.py # Post CRUD operations
|
|
│ └── ...
|
|
└── core/db/ # Database configuration
|
|
├── database.py # Connection and session setup
|
|
└── models.py # Base classes and mixins
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### Create with Validation
|
|
```python
|
|
@router.post("/users/", response_model=UserRead)
|
|
async def create_user(
|
|
user_data: UserCreate, # Validates input automatically
|
|
db: Annotated[AsyncSession, Depends(async_get_db)]
|
|
):
|
|
# Check for duplicates
|
|
if await crud_users.exists(db=db, email=user_data.email):
|
|
raise DuplicateValueException("Email already exists")
|
|
|
|
# Create user (password gets hashed automatically)
|
|
return await crud_users.create(db=db, object=user_data)
|
|
```
|
|
|
|
### Query with Filters
|
|
```python
|
|
# Get active users only
|
|
users = await crud_users.get_multi(
|
|
db=db,
|
|
is_active=True,
|
|
is_deleted=False,
|
|
offset=0,
|
|
limit=10
|
|
)
|
|
|
|
# Search users
|
|
users = await crud_users.get_multi(
|
|
db=db,
|
|
username__icontains="john", # Contains "john"
|
|
schema_to_select=UserRead
|
|
)
|
|
```
|
|
|
|
### Soft Delete Pattern
|
|
```python
|
|
# Soft delete (sets is_deleted=True)
|
|
await crud_users.delete(db=db, id=user_id)
|
|
|
|
# Hard delete (actually removes from database)
|
|
await crud_users.db_delete(db=db, id=user_id)
|
|
|
|
# Get only non-deleted records
|
|
users = await crud_users.get_multi(db=db, is_deleted=False)
|
|
```
|
|
|
|
## What's Next
|
|
|
|
Each guide builds on the previous one with practical examples:
|
|
|
|
1. **[Models](models.md)** - Define your database structure
|
|
2. **[Schemas](schemas.md)** - Add validation and serialization
|
|
3. **[CRUD Operations](crud.md)** - Implement business logic
|
|
4. **[Migrations](migrations.md)** - Deploy changes safely
|
|
|
|
The boilerplate provides a solid foundation - just follow these patterns to build your data layer! |