Best Practices - Write Better Code
📋 Best Practices
Section titled “📋 Best Practices”Great job building GameVault! 🎉 Now let’s cover the essential practices that will make your code cleaner and your projects more maintainable.
🧹 Code Organization
Section titled “🧹 Code Organization”Use Clear Names
Section titled “Use Clear Names”# ✅ Good - Self-documentingclass Game(AbstractEntity): title: str = Field(max_length=200) platform: str = Field(max_length=50)
# ❌ Confusing - Unclear purposeclass G(AbstractEntity): t: str p: strOrganize Files Logically
Section titled “Organize Files Logically”src/├── entity/ # Data models├── controller/ # Request handlers├── form/ # Form definitions├── repository/ # Data access└── service/ # Business logic💾 Database Best Practices
Section titled “💾 Database Best Practices”Always Create Migrations
Section titled “Always Create Migrations”# After changing any entity:framefox database create-migrationframefox database upgradeUse Descriptive Field Names
Section titled “Use Descriptive Field Names”# ✅ Clear purposerelease_date: Optional[date] = Field(default=None)completed_at: Optional[datetime] = Field(default=None)
# ❌ Ambiguousdate: Optional[date] = Field(default=None)when: Optional[datetime] = Field(default=None)🔒 Security Essentials
Section titled “🔒 Security Essentials”Validate All Input
Section titled “Validate All Input”# ✅ Always validate form dataif not title or len(title.strip()) == 0: form.add_error("title", "Title is required")
# ❌ Never trust user input directlygame.title = request.form.get("title") # Dangerous!Use Framefox Authentication
Section titled “Use Framefox Authentication”Framefox handles password hashing, session management, and CSRF protection automatically when you use the built-in authentication system.
🎯 Development Workflow
Section titled “🎯 Development Workflow”Test Your Changes
Section titled “Test Your Changes”# Start server and test manuallyframefox run
# Check for errors in browser console# Test different user scenarios# Verify database changes work correctlyUse Version Control
Section titled “Use Version Control”# Track your changesgit add .git commit -m "Add game rating feature"
# Create branches for new featuresgit checkout -b feature/game-covers🚀 Performance Tips
Section titled “🚀 Performance Tips”Keep Controllers Simple
Section titled “Keep Controllers Simple”# ✅ Delegate business logic to services@Route("/games", "game.create", methods=["POST"])async def create(self, request: Request): form = self.create_form(GameType) await form.handle_request(request)
if form.is_valid(): game = await self.game_service.create_game(form.data) return self.redirect("game.index")
return self.render("game/create.html", {"form": form})
# ❌ Don't put business logic in controllers@Route("/games", "game.create", methods=["POST"])async def create(self, request: Request): # 50 lines of business logic...Use Database Indexes for Searches
Section titled “Use Database Indexes for Searches”# Add indexes for frequently searched fieldsclass Game(AbstractEntity): title: str = Field(index=True) # Faster title searches platform: str = Field(index=True) # Faster platform filtering🎨 UI/UX Guidelines
Section titled “🎨 UI/UX Guidelines”Consistent Styling
Section titled “Consistent Styling”<!-- Use consistent Bootstrap classes --><div class="card mb-3"> <div class="card-body"> <h5 class="card-title">{{ game.title }}</h5> <span class="badge bg-primary">{{ game.platform }}</span> </div></div>Provide User Feedback
Section titled “Provide User Feedback”# Show success/error messagesif game_created: self.flash_success("Game added successfully!")else: self.flash_error("Failed to add game. Please try again.")🔧 Debug Like a Pro
Section titled “🔧 Debug Like a Pro”Use the Web Profiler
Section titled “Use the Web Profiler”Visit http://localhost:8000/_profiler to analyze:
- Request performance
- Database queries
- Template rendering
- Memory usage
Check Logs Regularly
Section titled “Check Logs Regularly”# View application logstail -f var/log/app.log
# Check for database issuesframefox debug database📁 Project Structure Tips
Section titled “📁 Project Structure Tips”Environment-Specific Config
Section titled “Environment-Specific Config”application: env: "${APP_ENV}" profiler: enabled: true # Only in developmentSecure Your Secrets
Section titled “Secure Your Secrets”# .env (never commit this file!)SESSION_SECRET_KEY=your-secret-keyDATABASE_URL=sqlite:///app.db
# Use environment variables in production🎯 When to Apply These Practices
Section titled “🎯 When to Apply These Practices”Starting Out: Focus on clear naming and basic organization
Growing Project: Add proper validation and error handling
Production Ready: Implement security, performance, and monitoring
Key Takeaway 💡
Section titled “Key Takeaway 💡”Good practices aren’t about perfect code from day one - they’re about sustainable development. Start simple, then gradually improve as your project grows.
Ready to explore what’s possible next? Check out What’s Next? for your learning journey! 🚀
{{ game.title }}
{{ game.platform }}
Give users feedback
Section titled “Give users feedback”# ✅ Tell users what happened in [controllers](/framefox/core/controllers)self.add_flash("success", f"'{game.title}' added to your collection!")self.add_flash("error", "Something went wrong. Please try again.")
# ❌ Silent failures are confusing# (nothing happens, user doesn't know why)� Debugging Tips
Section titled “� Debugging Tips”Use the debug commands
Section titled “Use the debug commands”# See all your pagesframefox debug router
# Check database statusframefox database status
# Clear cache when weird things happenframefox cache clearRead error messages carefully
Section titled “Read error messages carefully”When something breaks:
- Don’t panic! 😅
- Read the error message - it usually tells you what’s wrong
- Check the terminal where
framefox runis running - Google the error if you’re stuck
Test as you go
Section titled “Test as you go”Don’t build everything at once! Instead:
- Add one small feature
- Test it works
- Add the next feature
- Repeat
📚 Learning More
Section titled “📚 Learning More”Start small, grow gradually
Section titled “Start small, grow gradually”- GameVault taught you the basics
- Now try adding ONE new feature at a time
- Maybe add game screenshots? Or user reviews?
- Each small project teaches you something new
Read other people’s code
Section titled “Read other people’s code”- Look at open source Framefox projects on GitHub
- See how others solve similar problems
- Don’t copy blindly - understand WHY they did it that way
Don’t try to memorize everything
Section titled “Don’t try to memorize everything”- Keep this guide bookmarked
- Use the CLI reference when you forget commands
- Google is your friend!
- Every developer looks things up constantly
🚀 What’s Next?
Section titled “🚀 What’s Next?”You’ve built a real web application! Here are some ideas for your next steps:
Add more features to GameVault
Section titled “Add more features to GameVault”- User profiles with avatars
- Game screenshots and file uploads
- Game reviews and ratings
- Friends and social features
- Statistics and charts
Try a completely new project
Section titled “Try a completely new project”- Blog with posts and comments using entities
- Todo app with categories using forms
- Recipe manager with ingredients
- Bookmarks organizer
Learn new skills
Section titled “Learn new skills”- API development for mobile apps
- Real-time features with WebSockets
- Better design with custom CSS
- Deployment to share with friends
Remember 💡
Section titled “Remember 💡”- Every expert was once a beginner
- It’s okay to not know everything
- Building projects is the best way to learn
- Have fun with it!
You’ve already accomplished something awesome by building GameVault. Keep experimenting with controllers, forms, entities, and most importantly - enjoy the journey! 🎉
Keep coding with Framefox’s powerful tools, and welcome to the wonderful world of web development! 🚀✨
Controller Best Practices
Section titled “Controller Best Practices”Dependency Injection
Section titled “Dependency Injection”Use constructor injection for all dependencies:
class GameController(AbstractController): def __init__( self, template_renderer: TemplateRenderer, entity_manager: EntityManagerInterface, file_upload_service: FileUploadService, game_repository: GameRepository ): super().__init__() self.template_renderer = template_renderer self.entity_manager = entity_manager self.file_upload_service = file_upload_service self.game_repository = game_repositoryRoute Organization
Section titled “Route Organization”Group related routes in single controller:
class GameController(AbstractController): # List and display routes @Route("/games", "game.index", methods=["GET"]) async def index(self, request: Request): """List all games.""" pass
@Route("/games/{id:int}", "game.show", methods=["GET"]) async def show(self, request: Request, id: int): """Show single game.""" pass
# Form routes @Route("/games/add", "game.add", methods=["GET", "POST"]) async def add(self, request: Request): """Add new game.""" pass
@Route("/games/{id:int}/edit", "game.edit", methods=["GET", "POST"]) async def edit(self, request: Request, id: int): """Edit existing game.""" pass
# Action routes @Route("/games/{id:int}/delete", "game.delete", methods=["POST"]) async def delete(self, request: Request, id: int): """Delete game.""" passError Handling
Section titled “Error Handling”Implement consistent error handling:
@Route("/games/{id:int}", "game.show", methods=["GET"])async def show(self, request: Request, id: int): """Show game details.""" try: game = self.game_repository.find(id)
if not game: self.add_flash("error", "Game not found.") return self.redirect("/games")
# Check ownership user = self.get_user() if game.owner_id != user.id: self.add_flash("error", "Access denied.") return self.redirect("/games")
return self.template_renderer.render("game/show.html", { "title": game.title, "game": game })
except Exception as e: self.logger.error(f"Error showing game {id}: {e}") self.add_flash("error", "An error occurred while loading the game.") return self.redirect("/games")Form Handling Pattern
Section titled “Form Handling Pattern”Use consistent form handling pattern:
@Route("/games/add", "game.add", methods=["GET", "POST"])async def add(self, request: Request): """Add new game.""" user = self.get_user() if not user: return self.redirect("/login")
form = self.create_form(GameType)
if request.method == "POST": form_data = await request.form() form.submit(form_data)
if form.is_valid(): try: # Create entity game = Game( title=form.get_field_value("title"), description=form.get_field_value("description"), platform=form.get_field_value("platform"), status=form.get_field_value("status"), owner_id=user.id )
# Handle file upload cover_file = form_data.get("cover_image") if cover_file and cover_file.filename: filename = await self.file_upload_service.upload_cover( cover_file, user.id ) game.cover_image = filename
# Save to database self.entity_manager.persist(game) self.entity_manager.flush()
self.add_flash("success", f"'{game.title}' added to your collection!") return self.redirect("/games")
except Exception as e: self.logger.error(f"Error adding game: {e}") self.add_flash("error", "Failed to add game. Please try again.")
return self.template_renderer.render("game/add.html", { "title": "Add Game", "form": form })Form Design Patterns
Section titled “Form Design Patterns”Form Type Structure
Section titled “Form Type Structure”Organize form fields logically with proper validation:
class GameType(AbstractFormType): def build_form(self, builder: FormBuilder, options: dict): # Required fields first builder.add("title", TextType, { "label": "Game Title", "required": True, "constraints": [ LengthConstraint(min_length=1, max_length=200) ], "attr": { "class": "form-control", "placeholder": "Enter game title", "autofocus": True } })
# Optional fields builder.add("description", TextareaType, { "label": "Description", "required": False, "attr": { "class": "form-control", "rows": 4, "placeholder": "Describe the game..." } })
# Choice fields with enums builder.add("platform", ChoiceType, { "label": "Platform", "choices": [(p.value, p.get_display_name()) for p in GamePlatform], "required": True, "attr": {"class": "form-select"} })
# File upload fields builder.add("cover_image", FileType, { "label": "Cover Image", "required": False, "attr": { "class": "form-control", "accept": "image/*" }, "help": "Supported formats: JPG, PNG, GIF (max 5MB)" })Custom Validators
Section titled “Custom Validators”Create reusable custom validators:
class UniqueUsernameValidator(Constraint): """Validator to ensure username is unique."""
def __init__(self, user_repository: UserRepository, exclude_user_id: int = None): self.user_repository = user_repository self.exclude_user_id = exclude_user_id super().__init__()
def validate(self, value: str) -> bool: existing_user = self.user_repository.find_by_username(value)
if not existing_user: return True
# Allow current user to keep their username if self.exclude_user_id and existing_user.id == self.exclude_user_id: return True
return False
def get_message(self) -> str: return "This username is already taken."Service Layer Patterns
Section titled “Service Layer Patterns”Service Design
Section titled “Service Design”Create focused services with single responsibility:
class FileUploadService: """Service for handling file uploads."""
def __init__(self, upload_dir: str, allowed_extensions: List[str], max_size: int): self.upload_dir = Path(upload_dir) self.allowed_extensions = allowed_extensions self.max_size = max_size self.upload_dir.mkdir(parents=True, exist_ok=True)
async def upload_cover(self, file: UploadFile, user_id: int) -> str: """Upload game cover image.""" # Validate file self._validate_file(file)
# Generate unique filename filename = self._generate_filename(file.filename, user_id)
# Save file file_path = self.upload_dir / "games" / "covers" / filename file_path.parent.mkdir(parents=True, exist_ok=True)
content = await file.read() with open(file_path, "wb") as f: f.write(content)
return filename
def _validate_file(self, file: UploadFile) -> None: """Validate uploaded file.""" if not file.filename: raise ValueError("No file selected")
# Check file extension extension = file.filename.split(".")[-1].lower() if extension not in self.allowed_extensions: raise ValueError(f"File type not allowed. Allowed: {', '.join(self.allowed_extensions)}")
# Check file size (implement size check) # ... size validation logic
def _generate_filename(self, original_filename: str, user_id: int) -> str: """Generate unique filename.""" extension = original_filename.split(".")[-1].lower() timestamp = int(time.time()) random_string = secrets.token_hex(8) return f"{user_id}_{timestamp}_{random_string}.{extension}"Repository Patterns
Section titled “Repository Patterns”Keep repositories focused on data access:
class GameRepository(AbstractRepository): """Repository for Game entity."""
def __init__(self): super().__init__(Game)
def find_by_owner(self, owner_id: int, **kwargs) -> List[Game]: """Find games by owner with optional filters.""" filters = {"owner_id": owner_id}
# Add optional filters if "status" in kwargs: filters["status"] = kwargs["status"] if "platform" in kwargs: filters["platform"] = kwargs["platform"]
return self.find_by(filters, order_by={"created_at": "DESC"})
def search(self, owner_id: int, query: str) -> List[Game]: """Search games by title or description.""" # Implementation depends on database capabilities # This is a simplified version all_games = self.find_by_owner(owner_id) query_lower = query.lower()
return [ game for game in all_games if query_lower in game.title.lower() or (game.description and query_lower in game.description.lower()) ]
def get_statistics(self, owner_id: int) -> Dict[str, Any]: """Get comprehensive statistics for user's collection.""" games = self.find_by_owner(owner_id)
return { "total_games": len(games), "by_status": self._count_by_status(games), "by_platform": self._count_by_platform(games), "total_playtime": sum(g.playtime_hours or 0 for g in games), "average_rating": self._calculate_average_rating(games), "completion_rate": self._calculate_completion_rate(games) }
def _count_by_status(self, games: List[Game]) -> Dict[str, int]: """Count games by status.""" counts = {} for game in games: status = game.status.value counts[status] = counts.get(status, 0) + 1 return countsTemplate Best Practices
Section titled “Template Best Practices”Template Inheritance
Section titled “Template Inheritance”Use template inheritance effectively:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>{% block title %}GameVault{% endblock %}</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> {% block stylesheets %}{% endblock %}</head><body> <nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <!-- Navigation content --> </nav>
<main class="{% block main_class %}container my-4{% endblock %}"> {% block body %}{% endblock %} </main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> {% block javascripts %}{% endblock %}</body></html>
<!-- templates/game/base.html -->{% extends "base.html" %}
{% block title %}{{ title }} - GameVault{% endblock %}
{% block body %}<div class="row"> <div class="col-md-3"> {% block sidebar %} <div class="card"> <div class="card-header"> <h6>Game Management</h6> </div> <div class="card-body"> <a href="{{ path('game.add') }}" class="btn btn-primary btn-sm">Add Game</a> <a href="{{ path('game.index') }}" class="btn btn-outline-primary btn-sm">My Games</a> </div> </div> {% endblock %} </div> <div class="col-md-9"> {% block content %}{% endblock %} </div></div>{% endblock %}
<!-- templates/game/show.html -->{% extends "game/base.html" %}
{% block content %}<div class="card"> <div class="card-header"> <h1>{{ game.title }}</h1> </div> <div class="card-body"> <!-- Game details --> </div></div>{% endblock %}Component Templates
Section titled “Component Templates”Create reusable template components:
<div class="card game-card h-100"> {% if game.cover_image %} <img src="{{ asset('uploads/games/covers/' ~ game.cover_image) }}" class="card-img-top" alt="{{ game.title }}" style="height: 200px; object-fit: cover;"> {% else %} <div class="card-img-top bg-light d-flex align-items-center justify-content-center" style="height: 200px;"> <i class="fas fa-gamepad fa-3x text-muted"></i> </div> {% endif %}
<div class="card-body d-flex flex-column"> <h5 class="card-title">{{ game.title }}</h5> <p class="card-text">{{ game.platform.get_display_name() }}</p>
<div class="mt-auto"> <span class="badge {{ game.get_status_badge_class() }}"> {{ game.get_status_display() }} </span> {% if game.rating %} <div class="mt-2"> <small class="text-muted">{{ game.get_rating_stars() }}</small> </div> {% endif %} </div> </div>
<div class="card-footer"> <div class="btn-group w-100" role="group"> <a href="{{ path('game.show', {'id': game.id}) }}" class="btn btn-outline-primary btn-sm"> <i class="fas fa-eye"></i> View </a> <a href="{{ path('game.edit', {'id': game.id}) }}" class="btn btn-outline-secondary btn-sm"> <i class="fas fa-edit"></i> Edit </a> </div> </div></div>
<!-- Usage in list template --><div class="row"> {% for game in games %} <div class="col-md-6 col-lg-4 mb-4"> {% include "components/game_card.html" %} </div> {% endfor %}</div>Security Best Practices
Section titled “Security Best Practices”Input Validation
Section titled “Input Validation”Always validate user input:
# In formsbuilder.add("title", TextType, { "constraints": [ LengthConstraint(min_length=1, max_length=200), RegexConstraint( pattern=r"^[^<>\"'&]*$", message="Title contains invalid characters" ) ]})
# In controllers@Route("/games/{id:int}", "game.show", methods=["GET"])async def show(self, request: Request, id: int): # Validate ID parameter if id <= 0: self.add_flash("error", "Invalid game ID.") return self.redirect("/games")
# Check ownership user = self.get_user() game = self.game_repository.find(id)
if not game or game.owner_id != user.id: self.add_flash("error", "Game not found or access denied.") return self.redirect("/games")File Upload Security
Section titled “File Upload Security”Secure file upload handling:
class FileUploadService: ALLOWED_EXTENSIONS = {"jpg", "jpeg", "png", "gif", "webp"} MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB
def _validate_file(self, file: UploadFile) -> None: # Check file extension if not file.filename: raise ValueError("No file provided")
extension = file.filename.split(".")[-1].lower() if extension not in self.ALLOWED_EXTENSIONS: raise ValueError(f"File type not allowed: {extension}")
# Check file size file.file.seek(0, 2) # Seek to end size = file.file.tell() file.file.seek(0) # Reset to beginning
if size > self.MAX_FILE_SIZE: raise ValueError(f"File too large: {size} bytes")
# Validate file content (basic check) if not self._is_valid_image(file): raise ValueError("Invalid image file")
def _is_valid_image(self, file: UploadFile) -> bool: """Basic image validation.""" file.file.seek(0) header = file.file.read(16) file.file.seek(0)
# Check for common image file signatures if header.startswith(b'\xff\xd8'): # JPEG return True if header.startswith(b'\x89PNG'): # PNG return True if header.startswith(b'GIF8'): # GIF return True
return FalseCSRF Protection
Section titled “CSRF Protection”Use CSRF tokens in forms:
<!-- In templates --><form method="POST" action="{{ path('game.add') }}"> {{ csrf_token() }}
<!-- Form fields --> <div class="mb-3"> <label for="title" class="form-label">Title</label> <input type="text" class="form-control" id="title" name="title" required> </div>
<button type="submit" class="btn btn-primary">Add Game</button></form>Performance Optimization
Section titled “Performance Optimization”Database Optimization
Section titled “Database Optimization”Use efficient queries and eager loading:
# Avoid N+1 queries with relationshipsdef find_games_with_owners(self, limit: int = 20) -> List[Game]: """Load games with their owners in single query.""" return self.query().join(User).limit(limit).all()
# Use pagination for large datasetsdef find_paginated(self, page: int, per_page: int = 20) -> Dict[str, Any]: """Return paginated results.""" offset = (page - 1) * per_page games = self.find_by({}, limit=per_page, offset=offset) total = self.count({})
return { "games": games, "pagination": { "page": page, "per_page": per_page, "total": total, "pages": (total + per_page - 1) // per_page } }Caching Strategies
Section titled “Caching Strategies”Implement smart caching:
class GameStatsService: def __init__(self, cache_service: CacheService, game_repository: GameRepository): self.cache = cache_service self.game_repository = game_repository
def get_user_stats(self, user_id: int) -> Dict[str, Any]: """Get user statistics with caching.""" cache_key = f"user_stats:{user_id}"
# Try cache first stats = self.cache.get(cache_key) if stats: return stats
# Calculate stats stats = self.game_repository.get_statistics(user_id)
# Cache for 1 hour self.cache.set(cache_key, stats, ttl=3600)
return stats
def invalidate_user_stats(self, user_id: int) -> None: """Invalidate user stats cache.""" cache_key = f"user_stats:{user_id}" self.cache.delete(cache_key)Testing Patterns
Section titled “Testing Patterns”Unit Testing
Section titled “Unit Testing”Write focused unit tests:
import pytestfrom unittest.mock import Mock, patchfrom src.entity.user import Userfrom src.entity.game import Game, GameStatus, GamePlatformfrom src.repository.game_repository import GameRepository
class TestGameRepository: @pytest.fixture def mock_entity_manager(self): return Mock()
@pytest.fixture def game_repository(self, mock_entity_manager): repo = GameRepository() repo.entity_manager = mock_entity_manager return repo
def test_find_by_owner(self, game_repository): # Arrange user_id = 1 expected_games = [ Game(id=1, title="Game 1", owner_id=user_id), Game(id=2, title="Game 2", owner_id=user_id) ] game_repository.find_by = Mock(return_value=expected_games)
# Act result = game_repository.find_by_owner(user_id)
# Assert assert len(result) == 2 assert result[0].title == "Game 1" game_repository.find_by.assert_called_once_with( {"owner_id": user_id}, order_by={"created_at": "DESC"} )Integration Testing
Section titled “Integration Testing”Test full workflows:
import pytestfrom fastapi.testclient import TestClientfrom src.main import app
class TestGameController: @pytest.fixture def client(self): return TestClient(app)
@pytest.fixture def authenticated_user(self, client): # Create and authenticate test user response = client.post("/register", data={ "username": "testuser", "email": "test@example.com", "password": "password123" }) assert response.status_code == 302
response = client.post("/login", data={ "email": "test@example.com", "password": "password123" }) assert response.status_code == 302
return client
def test_add_game_success(self, authenticated_user): response = authenticated_user.post("/games/add", data={ "title": "Test Game", "platform": "pc", "status": "playing" })
assert response.status_code == 302 assert "/games" in response.headers["location"]
def test_add_game_validation_error(self, authenticated_user): response = authenticated_user.post("/games/add", data={ "title": "", # Empty title should fail validation "platform": "pc" })
assert response.status_code == 200 assert "form-error" in response.text or "is-invalid" in response.textThis comprehensive best practices guide covers the essential patterns and conventions for building professional Framefox applications. Following these practices will help you create maintainable, secure, and scalable applications like GameVault! 🚀