Skip to content
Back to skills

Backend Dev Guidelines 2

ASecurity

Comprehensive backend development guide for Python/FastAPI/SQLAlchemy applications. Use when creating routes, services, models, schemas, database operations, or working with FastAPI endpoints, SQLAlchemy/Alembic migrations, Pydantic validation, error handling, dependency injection, or async patterns. Covers layered architecture (routes → services → repositories → models), separation of concerns, error handling, performance, testing strategies, and best practices.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 27, 2026
databasespythongobashsqlfastapitestingrefactoringapidatabasebackend

Works with

  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 3 files and shows the line behind each finding

Scanned September 27, 2026

npx -y skills add David-Li0406/meta-skill-evloving --skill backend-dev-guidelines-2 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Backend Dev Guidelines 2?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Backend Dev Guidelines 2
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/david-li0406-backend-dev-guidelines-2/badge)](https://www.skillsdirectory.com/skills/david-li0406-backend-dev-guidelines-2)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: backend-dev-guidelines
description: Comprehensive backend development guide for Python/FastAPI/SQLAlchemy applications. Use when creating routes, services, models, schemas, database operations, or working with FastAPI endpoints, SQLAlchemy/Alembic migrations, Pydantic validation, error handling, dependency injection, or async patterns. Covers layered architecture (routes → services → repositories → models), separation of concerns, error handling, performance, testing strategies, and best practices.
---

# Backend Development Guidelines (FastAPI/Python)

## Purpose

Establish consistency and best practices for Python/FastAPI backend development using modern FastAPI/SQLAlchemy/Pydantic patterns.

## When to Use This Skill

Automatically activates when working on:
- Creating or modifying routes, endpoints, APIs
- Building services, repositories
- Database models and schemas (SQLAlchemy/Pydantic)
- Alembic migrations
- Input validation with Pydantic
- Error handling and exception management
- Dependency injection
- Backend testing and refactoring

---

## Quick Start

### New Backend Feature Checklist

- [ ] **Route**: Clean FastAPI route definition, delegate to service
- [ ] **Service**: Business logic with dependency injection
- [ ] **Repository**: Database access layer (if complex queries)
- [ ] **Model**: SQLAlchemy model (database table)
- [ ] **Schema**: Pydantic schema (validation + serialization)
- [ ] **Migration**: Alembic migration for schema changes
- [ ] **Validation**: Pydantic validators
- [ ] **Error Handling**: Proper HTTPException with status codes
- [ ] **Tests**: Unit + integration tests
- [ ] **Documentation**: FastAPI auto-docs (/docs)

---

## Architecture Overview

### Layered Architecture

```
HTTP Request
    ↓
Routes (FastAPI endpoints - routing only)
    ↓
Services (business logic)
    ↓
Repositories (complex database operations)
    ↓
Models (SQLAlchemy ORM)
    ↓
Database (PostgreSQL via Supabase)
```

**Key Principle:** Each layer has ONE responsibility.

**Current Project Structure:**
```
backend/app/
├── routes/              # API routes (ML_Routes.py, Reddit_Routes.py)
├── services/            # Business logic (TO BE CREATED)
├── repositories/        # Data access (TO BE CREATED as needed)
├── models.py            # SQLAlchemy models (City, Project, Article, ArticleChunk)
├── schemas.py           # Pydantic schemas (validation + serialization)
├── db.py                # Database connection and session management
├── main.py              # FastAPI app setup and CRUD endpoints
└── machine_learning/    # ML-specific logic
```

---

## Core Principles (7 Key Rules)

### 1. Routes Only Route, Services Handle Logic

```python
# ❌ NEVER: Business logic directly in routes
@app.post("/complex-operation")
async def complex_operation(data: dict, db: Session = Depends(get_db)):
    # 50 lines of business logic
    # Database queries
    # Validation
    # Error handling
    return result

# ✅ ALWAYS: Delegate to service
@app.post("/complex-operation")
async def complex_operation(
    data: ComplexOperationRequest,
    db: Session = Depends(get_db)
):
    result = await operation_service.execute(data, db)
    return result
```

**Why:** Testability, reusability, maintainability

### 2. Use Pydantic for ALL Input/Output

```python
# ❌ NEVER: Untyped dict parameters
@app.post("/create-user")
def create_user(user_data: dict):
    name = user_data.get("name")  # No validation!

# ✅ ALWAYS: Pydantic schema
class UserCreate(BaseModel):
    name: str
    email: EmailStr
    age: int = Field(ge=0, le=120)

@app.post("/create-user", response_model=UserResponse)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    # Already validated!
    db_user = models.User(**user.model_dump())
    db.add(db_user)
    db.commit()
    return db_user
```

### 3. Separate Models (DB) from Schemas (API)

```python
# models.py - SQLAlchemy (database)
class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    email = Column(String, unique=True, nullable=False)
    hashed_password = Column(String, nullable=False)  # NEVER expose!
    created_at = Column(DateTime, default=datetime.utcnow)

# schemas.py - Pydantic (API)
class UserCreate(BaseModel):
    email: EmailStr
    password: str  # Plain password for input

class UserResponse(BaseModel):
    id: int
    email: str
    created_at: datetime
    # NO hashed_password - never expose to API!

    model_config = ConfigDict(from_attributes=True)
```

**Why:** Security, flexibility, separation of concerns

### 4. Use Dependency Injection

```python
# ❌ NEVER: Global db connection
db = SessionLocal()  # Dangerous!

@app.get("/users")
def get_users():
    users = db.query(User).all()  # What if db closed?
    return users

# ✅ ALWAYS: Dependency injection
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/users", response_model=List[UserResponse])
def get_users(db: Session = Depends(get_db)):
    users = db.query(User).all()
    return users
```

**Current Project Pattern:** Already using this correctly in `main.py`! Keep it up.

### 5. Proper Error Handling with HTTPException

```python
# ❌ NEVER: Generic exceptions or silent failures
@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    return user  # Returns None if not found - confusing!

# ✅ ALWAYS: Explicit HTTP exceptions
from fastapi import HTTPException, status

@app.get("/users/{user_id}", response_model=UserResponse)
def get_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"User {user_id} not found"
        )
    return user
```

**Available Status Codes:**
- `201`: Created
- `204`: No Content (delete successful)
- `400`: Bad Request (validation error)
- `404`: Not Found
- `409`: Conflict (duplicate)
- `422`: Unprocessable Entity (Pydantic validation)
- `500`: Internal Server Error

### 6. Transaction Management

```python
# ❌ RISKY: Manual commit without error handling
def create_order(order_data: OrderCreate, db: Session):
    order = Order(**order_data.model_dump())
    db.add(order)
    db.commit()  # What if this fails?

    item = OrderItem(order_id=order.id, ...)
    db.add(item)
    db.commit()  # Item created even if next step fails!

# ✅ SAFE: Transaction with rollback
def create_order(order_data: OrderCreate, db: Session):
    try:
        # Create order
        order = Order(**order_data.model_dump())
        db.add(order)
        db.flush()  # Get order.id without committing

        # Create items
        for item_data in order_data.items:
            item = OrderItem(order_id=order.id, **item_data.model_dump())
            db.add(item)

        # Commit everything together
        db.commit()
        db.refresh(order)
        return order

    except Exception as e:
        db.rollback()  # Rollback everything
        raise HTTPException(status_code=500, detail=str(e))
```

**Current Project Pattern:** Your `ingest_json_payload` uses this pattern correctly with try/except and commit at the end!

### 7. Use Type Hints Everywhere

```python
# ❌ NEVER: No types
def process_data(data):
    result = transform(data)
    return result

# ✅ ALWAYS: Full typing
from typing import List, Optional, Dict, Any

def process_data(
    data: List[Dict[str, Any]],
    db: Session
) -> Optional[ProcessedResult]:
    result: ProcessedResult = transform(data)
    return result
```

---

## Directory Structure (Recommended Evolution)

### Current Structure
```
backend/app/
├── main.py              # Has routes, CRUD, ingest logic
├── models.py
├── schemas.py
├── db.py
└── routes/
    ├── ML_Routes.py
    └── Reddit_Routes.py
```

### Recommended Structure (As Project Grows)
```
backend/app/
├── main.py              # FastAPI app setup only
├── db.py
├── models.py            # Or split into models/
├── schemas.py           # Or split into schemas/
├── routes/
│   ├── __init__.py
│   ├── cities.py
│   ├── projects.py
│   ├── articles.py
│   ├── ml.py
│   └── reddit.py
├── services/            # Business logic
│   ├── __init__.py
│   ├── city_service.py
│   ├── project_service.py
│   ├── article_service.py
│   ├── ingest_service.py
│   └── ml_service.py
├── repositories/        # Complex queries
│   ├── __init__.py
│   └── article_repository.py  # Only if needed
├── utils/
│   └── helpers.py
└── tests/
    ├── test_routes.py
    ├── test_services.py
    └── conftest.py
```

**When to create a service:**
- Logic is >20 lines
- Logic is reused in multiple endpoints
- Logic involves multiple models/tables
- Business rules need testing independently

**When to create a repository:**
- Complex queries with joins
- Query reuse across services
- Query optimization needed
- Raw SQL required

---

## Detailed Guides

### [Architecture & Patterns](resources/architecture-patterns.md)
- Layered architecture deep dive
- When to extract services
- When to create repositories
- Dependency injection patterns

### [Models & Schemas](resources/models-and-schemas.md)
- SQLAlchemy model patterns
- Pydantic schema patterns
- Relationships (ForeignKey, Many-to-Many)
- Model validators and computed fields

### [Database Patterns](resources/database-patterns.md)
- Session management
- Transaction patterns
- Query optimization
- Alembic migrations

### [Routing & Endpoints](resources/routing-and-endpoints.md)
- FastAPI route patterns
- Path parameters, query parameters, body
- Response models
- Status codes

### [Error Handling](resources/error-handling.md)
- HTTPException patterns
- Custom exception handlers
- Validation errors
- Logging and monitoring

### [Testing Guide](resources/testing-guide.md)
- pytest setup
- Testing with database
- Fixtures and factories
- Integration tests

### [Performance & Optimization](resources/performance.md)
- Async vs sync
- Database query optimization
- Caching patterns
- Connection pooling

---

## Package Management

**This project uses `uv` for Python package management.**

```bash
# Add a new dependency
uv add <package-name>

# Add a dev dependency
uv add --dev <package-name>

# Install dependencies
uv sync

# Run Python with uv
uv run python script.py

# Run uvicorn with uv
uv run uvicorn app.main:app --reload
```

**❌ NEVER use `pip install`** - Always use `uv add` instead.

---

## Migration Strategy (From Current to Recommended)

**Phase 1: Extract Services (Do this when routes get >30 lines)**
1. Create `services/` directory
2. Move business logic from `main.py` routes to services
3. Keep routes thin

**Phase 2: Split Routes (Do this when main.py gets >500 lines)**
1. Create separate route files by resource
2. Use `APIRouter` for each resource
3. Include routers in `main.py`

**Phase 3: Add Repositories (Only if needed)**
1. If you have complex joins/queries
2. Create `repositories/` directory
3. Move query logic from services to repositories

**Don't over-engineer early!** Start simple, refactor when needed.

---

## Common Patterns

### Pattern: Get or Create

```python
# Common pattern for ensuring entities exist
def get_or_create_city(city_name: str, db: Session) -> models.City:
    city = db.query(models.City).filter(
        models.City.city_name == city_name
    ).first()

    if not city:
        city = models.City(city_name=city_name)
        db.add(city)
        db.flush()  # Get city.id without full commit

    return city
```

### Pattern: Bulk Operations

```python
# Efficient bulk insert
def bulk_create_articles(
    articles: List[ArticleCreate],
    db: Session
) -> List[models.Article]:
    db_articles = [
        models.Article(**article.model_dump())
        for article in articles
    ]
    db.bulk_save_objects(db_articles)
    db.commit()
    return db_articles
```

### Pattern: Pagination

```python
from fastapi import Query

@app.get("/articles")
def get_articles(
    skip: int = Query(0, ge=0),
    limit: int = Query(20, ge=1, le=100),
    db: Session = Depends(get_db)
):
    total = db.query(models.Article).count()
    articles = db.query(models.Article).offset(skip).limit(limit).all()

    return {
        "total": total,
        "skip": skip,
        "limit": limit,
        "results": articles
    }
```

### Pattern: Search with Filters

```python
@app.get("/search")
def search_articles(
    query: Optional[str] = None,
    city: Optional[str] = None,
    project: Optional[str] = None,
    db: Session = Depends(get_db)
):
    articles_query = db.query(models.Article)

    if query:
        articles_query = articles_query.filter(
            or_(
                models.Article.title.ilike(f"%{query}%"),
                models.Article.full_text.ilike(f"%{query}%")
            )
        )

    if city:
        articles_query = articles_query.join(models.Article.projects).join(
            models.Project.city
        ).filter(models.City.city_name.ilike(f"%{city}%"))

    return articles_query.all()
```

---

## Quick Reference

### FastAPI Decorators
```python
@app.get("/path")           # GET request
@app.post("/path")          # POST request
@app.put("/path/{id}")      # PUT request (full update)
@app.patch("/path/{id}")    # PATCH request (partial update)
@app.delete("/path/{id}")   # DELETE request
```

### Common Imports
```python
from fastapi import FastAPI, HTTPException, Depends, status, Query, Path, Body
from sqlalchemy.orm import Session
from typing import List, Optional
from pydantic import BaseModel, Field, validator
```

### Pydantic Config
```python
class UserResponse(BaseModel):
    id: int
    name: str

    # For SQLAlchemy models
    model_config = ConfigDict(from_attributes=True)
```

---

## Resources

- [FastAPI Docs](https://fastapi.tiangolo.com/)
- [SQLAlchemy ORM Tutorial](https://docs.sqlalchemy.org/en/20/tutorial/)
- [Pydantic Docs](https://docs.pydantic.dev/)
- [Alembic Tutorial](https://alembic.sqlalchemy.org/en/latest/tutorial.html)

---

**Remember:** Start simple, refactor when complexity demands it. Your current structure in `main.py` is fine for early development. Extract to services/repositories as routes grow beyond 30-50 lines.

Files in this skill

  • SKILL.md14.3 KB
  • resources/architecture-patterns.md9.7 KB
  • resources/models-and-schemas.md8.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…