# 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` ```python # ✅ 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` ```python # ✅ 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 ```python # ✅ 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()` ```python # ✅ 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 ```python # ✅ 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 ```python # ✅ 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 ```python # ✅ 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` ```python # ✅ 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 ```python 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 ```python 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`: ```python # 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