initial commit
This commit is contained in:
235
docs/user-guide/database/index.md
Normal file
235
docs/user-guide/database/index.md
Normal file
@ -0,0 +1,235 @@
|
||||
# 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!
|
||||
Reference in New Issue
Block a user