kanban-app/docs/usage_rules_backend.md

8.5 KiB

Backend Development Rules for AI/LLM

This document provides guidelines and rules that AI/LLM assistants should follow when writing or modifying backend code for the Crafting Shop application.

Architecture Principles

Application Factory Pattern

  • ALWAYS use the application factory pattern via create_app() in backend/app/__init__.py
  • NEVER initialize Flask extensions globally - create them at module level, initialize in create_app()
  • NEVER create multiple SQLAlchemy instances - use the db extension from app/__init__.py
# ✅ CORRECT
from app import db

class User(db.Model):
    pass

# ❌ WRONG
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

Import Patterns

  • Import db and other extensions from app module, not flask_sqlalchemy directly
  • Import models from the app.models package (e.g., from app.models import User)
  • All routes should import db from app
# ✅ CORRECT
from app import db
from app.models import User, Product, Order

# ❌ WRONG
from flask_sqlalchemy import SQLAlchemy
from app.models.user import User  # Don't import from individual files

Code Style

Formatting

  • Use double quotes for strings and dictionary keys
  • Follow PEP 8 style guide
  • Use meaningful variable and function names
  • Maximum line length: 100 characters

Type Hints

  • Add type hints to function signatures where appropriate
  • Use Python 3.11+ type hinting features

Model Rules

Database Models

  • ALL models must import db from app: from app import db
  • ALL models must inherit from db.Model
  • NEVER create your own db = SQLAlchemy() instance
  • Use __tablename__ explicitly
  • Use to_dict() method for serialization
  • Use __repr__() for debugging
# ✅ CORRECT
from app import db
from datetime import datetime

class Product(db.Model):
    __tablename__ = "products"
    
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(200), nullable=False, index=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    
    def to_dict(self):
        return {"id": self.id, "name": self.name}

Relationships

  • Use back_populates for bidirectional relationships
  • Use lazy="dynamic" for one-to-many collections when appropriate
  • Define foreign keys explicitly with db.ForeignKey()

Route/API Rules

Blueprint Usage

  • ALL routes must be defined in blueprints
  • Register blueprints in create_app() in backend/app/__init__.py
  • Group related routes by functionality

Request Handling

  • Use request.get_json() for JSON payloads
  • Always validate input data
  • Return proper HTTP status codes
  • Return JSON responses with jsonify()
# ✅ CORRECT
from flask import Blueprint, request, jsonify
from app import db

api_bp = Blueprint("api", __name__)

@api_bp.route("/products", methods=["POST"])
@jwt_required()
def create_product():
    data = request.get_json()
    
    if not data or not data.get("name"):
        return jsonify({"error": "Name is required"}), 400
    
    product = Product(name=data["name"])
    db.session.add(product)
    db.session.commit()
    
    return jsonify(product.to_dict()), 201

Database Operations

  • NEVER access db through current_app.extensions['sqlalchemy'].db
  • ALWAYS import db from app
  • Use db.session.add() and db.session.commit() for transactions
  • Use db.session.flush() when you need the ID before commit

Error Handling

  • Handle common errors (404, 400, 401, 403, 500)
  • Return JSON error responses with consistent format
  • Use Flask error handlers where appropriate
# ✅ CORRECT
@app.errorhandler(404)
def not_found(error):
    return jsonify({"error": "Not found"}), 404

Authentication

  • Use @jwt_required() decorator for protected routes
  • Use get_jwt_identity() to get current user ID
  • Verify user permissions in routes
# ✅ CORRECT
from flask_jwt_extended import jwt_required, get_jwt_identity

@api_bp.route("/orders", methods=["POST"])
@jwt_required()
def create_order():
    user_id = get_jwt_identity()
    # ... rest of code

Configuration

Environment Variables

  • ALL configuration must use environment variables via os.environ.get()
  • Provide sensible defaults in app/config.py
  • Use .env.example for documentation

CORS

  • Configure CORS in create_app() using the cors extension
  • Use CORS_ORIGINS environment variable
# ✅ CORRECT
cors.init_app(app, resources={r"/api/*": {"origins": app.config.get('CORS_ORIGINS', '*')}})

Service Layer Rules

Business Logic

  • Place complex business logic in backend/app/services/
  • Services should be stateless functions or classes
  • Services should import db from app
# ✅ CORRECT
from app import db
from app.models import Product

def create_product_service(data):
    product = Product(**data)
    db.session.add(product)
    db.session.commit()
    return product

Testing Rules

Test Structure

  • Use pytest framework
  • Place tests in backend/tests/
  • Use fixtures for common setup

Database in Tests

  • Use in-memory SQLite for tests
  • Clean up database between tests
  • Use pytest.fixture for database setup

Security Rules

Password Handling

  • NEVER store plain text passwords
  • Use werkzeug.security for password hashing
  • Use set_password() and check_password() methods

SQL Injection Prevention

  • ALWAYS use SQLAlchemy ORM, never raw SQL
  • Use parameterized queries if raw SQL is absolutely necessary

Input Validation

  • Validate all user inputs
  • Sanitize data before database operations
  • Use Flask-WTF for form validation

File Structure for New Features

When adding new features, follow this structure:

backend/app/
├── models/
│   └── feature_name.py          # Database models
├── routes/
│   └── feature_name.py           # API routes
├── services/
│   └── feature_name.py           # Business logic
└── utils/
    └── feature_name.py           # Utility functions

Common Patterns

Creating a New Model

from app import db
from datetime import datetime

class NewModel(db.Model):
    __tablename__ = "new_model"
    
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(200), nullable=False, index=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    def to_dict(self):
        return {
            "id": self.id,
            "name": self.name,
            "created_at": self.created_at.isoformat() if self.created_at else None
        }

Creating New Routes

from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from app import db
from app.models import NewModel

new_bp = Blueprint("new", __name__)

@new_bp.route("/resources", methods=["GET"])
def get_resources():
    resources = NewModel.query.all()
    return jsonify([r.to_dict() for r in resources]), 200

@new_bp.route("/resources", methods=["POST"])
@jwt_required()
def create_resource():
    data = request.get_json()
    resource = NewModel(**data)
    db.session.add(resource)
    db.session.commit()
    return jsonify(resource.to_dict()), 201

Registering New Blueprints

In backend/app/__init__.py:

# Import models
from app.models.new_model import NewModel

# Register blueprint
from app.routes.feature_name import new_bp
app.register_blueprint(new_bp, url_prefix="/api/new")

DO NOT DO List

NEVER create db = SQLAlchemy() in model files NEVER access db via current_app.extensions['sqlalchemy'].db NEVER initialize Flask extensions before create_app() is called NEVER use raw SQL queries without proper escaping NEVER store secrets in code (use environment variables) NEVER return plain Python objects (use jsonify()) NEVER use single quotes for dictionary keys NEVER commit transactions without proper error handling NEVER ignore CORS configuration NEVER skip input validation

Checklist Before Committing

  • All models import db from app
  • All routes import db from app
  • No db = SQLAlchemy() in model files
  • All routes are in blueprints
  • Proper error handling in place
  • Environment variables used for configuration
  • Type hints added to functions
  • Tests written for new functionality
  • Documentation updated
  • Code follows PEP 8 style guide