Python Full Stack Web Developer Course

Week 3: Python Fundamentals (Part 2)

Friday Morning: Python Project Structure for Web Applications

Building a Solid Foundation: Project Structure for Web Applications

Welcome to our session on Python project structure for web applications! Today, we'll explore one of the most fundamental but often overlooked aspects of successful web development: how to organize your code and files effectively.

As we prepare to dive into web frameworks in the coming weeks, understanding how to structure your Python web applications becomes crucial. The organization of your project can greatly impact its maintainability, scalability, and the ease with which new team members can understand and contribute to it.

Why Project Structure Matters

Analogy: Think of your project structure as the blueprint for a building. Without a clear, well-thought-out blueprint, even the finest materials and skilled workers will struggle to create a functional, durable structure. Similarly, without a good project organization, even excellent code and talented developers will struggle to build a maintainable application.

The benefits of a well-structured project include:

Real-world Impact: Major tech companies, including those building Python web applications like Instagram, Dropbox, and Pinterest, have dedicated significant effort to creating optimal project structures that allow hundreds of developers to work effectively on the same codebase.

Common Python Project Structure Patterns

Let's explore the most common patterns for structuring Python web applications:

The Simple Flask Application

For small, straightforward applications, a simple structure might look like this:

my_flask_app/
├── app.py               # Application entry point
├── config.py            # Configuration settings
├── requirements.txt     # Dependencies
├── static/              # Static files (CSS, JS, images)
│   ├── css/
│   ├── js/
│   └── images/
├── templates/           # HTML templates
│   ├── base.html
│   ├── index.html
│   └── other_pages/
└── venv/                # Virtual environment (not in version control)

When to use: Simple applications, prototypes, learning projects, or microservices that perform a single function.

Limitations: Quickly becomes unwieldy as the application grows. Logic and presentation become mixed, making maintenance challenging.

The Package-Based Structure

As applications grow, organizing code into a proper Python package provides more structure:

my_web_app/
├── myapp/                    # Main package
│   ├── __init__.py           # Package initialization
│   ├── models.py             # Data models
│   ├── routes.py             # URL route definitions
│   ├── forms.py              # Form definitions
│   ├── static/               # Static files
│   │   ├── css/
│   │   ├── js/
│   │   └── images/
│   └── templates/            # Templates
│       ├── base.html
│       └── pages/
├── config.py                 # Configuration
├── requirements.txt          # Dependencies
├── run.py                    # Application entry point
└── README.md                 # Documentation

When to use: Medium-sized applications where a simple structure is becoming insufficient, but full modularity isn't yet necessary.

Advantages: Clear separation of code into logical files, better import mechanisms as a package.

The Modular Blueprint Structure

For larger applications, breaking functionality into modules or "blueprints" (in Flask terminology) provides better organization:

my_web_app/
├── myapp/
│   ├── __init__.py           # App initialization, blueprint registration
│   ├── extensions.py         # Flask extensions initialization
│   ├── models/               # Database models
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── product.py
│   ├── auth/                 # Authentication module
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   ├── models.py
│   │   ├── forms.py
│   │   └── templates/
│   │       └── auth/
│   ├── admin/                # Admin panel module
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   └── templates/
│   │       └── admin/
│   ├── public/               # Public-facing module
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   └── templates/
│   │       └── public/
│   ├── static/
│   └── templates/
│       └── base.html
├── migrations/               # Database migrations
├── tests/                    # Test suite
│   ├── __init__.py
│   ├── conftest.py
│   ├── test_auth.py
│   └── test_models.py
├── config.py
├── requirements.txt
├── requirements-dev.txt
├── run.py
└── README.md

When to use: Larger applications with distinct, separable features or when multiple developers work on different parts of the application.

Advantages: Logical separation of concerns, ability to work on modules independently.

The Domain-Driven Design Structure

For complex applications, organizing by business domain rather than technical function can be more effective:

my_web_app/
├── myapp/
│   ├── __init__.py
│   ├── extensions.py
│   ├── core/                  # Shared core functionality
│   │   ├── __init__.py
│   │   ├── database.py
│   │   ├── security.py
│   │   └── utils.py
│   ├── users/                 # Users domain
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── services.py        # Business logic
│   │   ├── schemas.py         # Serialization/validation
│   │   ├── views.py           # API/route handlers
│   │   └── tests/             # Domain-specific tests
│   ├── products/              # Products domain
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── services.py
│   │   ├── schemas.py
│   │   ├── views.py
│   │   └── tests/
│   ├── orders/                # Orders domain
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── services.py
│   │   ├── schemas.py
│   │   ├── views.py
│   │   └── tests/
│   ├── static/
│   └── templates/
├── migrations/
├── tests/                     # Integration tests
├── config.py
├── requirements/              # Split requirements
│   ├── base.txt
│   ├── dev.txt
│   └── prod.txt
├── wsgi.py                    # WSGI entry point
└── README.md

When to use: Complex enterprise applications with clear business domains and complex business logic.

Advantages: Focuses on business domains rather than technical layers, making it easier to understand the business purpose of code.

The Hexagonal Architecture

For applications with complex business logic and multiple interfaces (web, API, CLI), a hexagonal (ports and adapters) structure can be effective:

my_web_app/
├── myapp/
│   ├── __init__.py
│   ├── domain/               # Business logic, domain models, and rules
│   │   ├── __init__.py
│   │   ├── user/
│   │   │   ├── __init__.py
│   │   │   ├── entities.py   # Domain models
│   │   │   ├── repositories.py  # Abstract repositories
│   │   │   ├── services.py   # Business logic
│   │   │   └── exceptions.py
│   │   └── order/
│   │       ├── __init__.py
│   │       ├── entities.py
│   │       ├── repositories.py
│   │       ├── services.py
│   │       └── exceptions.py
│   ├── interfaces/           # User interfaces (ports)
│   │   ├── __init__.py
│   │   ├── web/              # Web interface
│   │   │   ├── __init__.py
│   │   │   ├── routes.py
│   │   │   ├── forms.py
│   │   │   └── templates/
│   │   └── api/              # API interface
│   │       ├── __init__.py
│   │       ├── routes.py
│   │       └── schemas.py
│   └── infrastructure/       # Technical implementations (adapters)
│       ├── __init__.py
│       ├── database.py
│       ├── repositories/     # Concrete repositories
│       │   ├── __init__.py
│       │   ├── sql_user_repository.py
│       │   └── sql_order_repository.py
│       ├── security.py
│       └── external_services.py  # 3rd party services
├── config/                   # Configuration
│   ├── __init__.py
│   ├── development.py
│   ├── production.py
│   └── testing.py
├── tests/
├── requirements/
├── wsgi.py
└── README.md

When to use: Complex applications with rich domain logic and multiple interfaces/entry points.

Advantages: Strong separation of concerns, business logic isolated from technical details, adaptable to changing requirements.

Metaphor: Think of this architecture as a hexagon with your core business logic in the center, protected from the outside world. All interactions with external systems (databases, web, API) go through well-defined ports (interfaces) that are implemented by adapters. This keeps your business logic pure and free from external dependencies.

Shared Components Across Structures

Regardless of the structure you choose, certain components are commonly found in Python web applications:

Entry Points

Files that start the application:

# run.py (for development)
from myapp import create_app

app = create_app()

if __name__ == '__main__':
    app.run(debug=True)

# wsgi.py (for production)
from myapp import create_app

app = create_app()

# Used by WSGI servers like Gunicorn
# gunicorn wsgi:app

Application Factory

A function that creates and configures the application:

# myapp/__init__.py
from flask import Flask

def create_app(config_object='config.ProductionConfig'):
    """Application factory function."""
    app = Flask(__name__)
    app.config.from_object(config_object)
    
    # Initialize extensions
    from myapp.extensions import db, migrate
    db.init_app(app)
    migrate.init_app(app, db)
    
    # Register blueprints
    from myapp.auth.routes import auth_bp
    from myapp.public.routes import public_bp
    app.register_blueprint(auth_bp)
    app.register_blueprint(public_bp)
    
    return app

Advantages: Allows creating multiple instances with different configurations (e.g., testing vs. production), easier extension initialization.

Configuration

Separating configuration from code:

# config.py
import os

class Config:
    """Base configuration."""
    SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-please-change')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

class DevelopmentConfig(Config):
    """Development configuration."""
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'

class ProductionConfig(Config):
    """Production configuration."""
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
    
class TestingConfig(Config):
    """Testing configuration."""
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'

Best Practice: Use environment variables for sensitive values and production settings.

Models

Representing database entities:

# myapp/models/user.py
from myapp.extensions import db
from werkzeug.security import generate_password_hash, check_password_hash

class User(db.Model):
    """User model for authentication."""
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(128))
    
    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
        
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)
        
    def __repr__(self):
        return f'<User {self.username}>'

Views/Routes

Handling HTTP requests:

# myapp/auth/routes.py
from flask import Blueprint, render_template, redirect, url_for, flash
from flask_login import login_user, logout_user, login_required

from myapp.auth.forms import LoginForm, RegisterForm
from myapp.models.user import User
from myapp.extensions import db

auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user and user.check_password(form.password.data):
            login_user(user)
            return redirect(url_for('main.index'))
        flash('Invalid username or password')
    return render_template('auth/login.html', form=form)

Templates

Separating presentation from logic:

<!-- myapp/templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}My Web App{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
</head>
<body>
    <header>
        <nav>
            <ul>
                <li><a href="{{ url_for('public.index') }}">Home</a></li>
                {% if current_user.is_authenticated %}
                    <li><a href="{{ url_for('auth.logout') }}">Logout</a></li>
                {% else %}
                    <li><a href="{{ url_for('auth.login') }}">Login</a></li>
                    <li><a href="{{ url_for('auth.register') }}">Register</a></li>
                {% endif %}
            </ul>
        </nav>
    </header>
    
    <main>
        {% block content %}{% endblock %}
    </main>
    
    <footer>
        <p>© {{ now.year }} My Web App</p>
    </footer>
</body>
</html>

Static Files

Organizing CSS, JavaScript, and images:

static/
├── css/
│   ├── main.css           # Main stylesheets
│   ├── components/        # Component-specific styles
│   └── vendor/            # Third-party CSS
├── js/
│   ├── main.js            # Main JavaScript
│   ├── components/        # Component-specific scripts
│   └── vendor/            # Third-party scripts
└── images/
    ├── logo.png
    ├── icons/
    └── backgrounds/

Best Practice: Consider using a frontend build system for complex static assets.

Tests

Organizing tests to mirror the application structure:

tests/
├── conftest.py           # Shared fixtures
├── test_config.py        # Configuration tests
├── functional/           # End-to-end tests
│   ├── test_auth.py
│   └── test_public.py
└── unit/                 # Unit tests
    ├── test_models.py
    └── test_forms.py
# tests/conftest.py
import pytest
from myapp import create_app
from myapp.extensions import db as _db

@pytest.fixture
def app():
    """Create and configure a Flask app for testing."""
    app = create_app('config.TestingConfig')
    with app.app_context():
        _db.create_all()
        yield app
        _db.session.remove()
        _db.drop_all()
        
@pytest.fixture
def client(app):
    """A test client for the app."""
    return app.test_client()

@pytest.fixture
def db(app):
    """Database for testing."""
    return _db

Framework-Specific Structure Considerations

Different frameworks have their own conventions and best practices for project structure:

Flask

Recommended Structure: The Modular Blueprint structure works well for most Flask applications.

Django

Django follows a more opinionated structure:

my_django_project/
├── manage.py              # Command-line utility
├── myproject/             # Project package
│   ├── __init__.py
│   ├── settings/          # Split settings
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── development.py
│   │   └── production.py
│   ├── urls.py            # Main URL configuration
│   ├── asgi.py            # ASGI configuration
│   └── wsgi.py            # WSGI configuration
├── apps/                  # Custom application modules
│   ├── users/             # Users app
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── migrations/
│   │   ├── models.py
│   │   ├── tests.py
│   │   ├── urls.py
│   │   └── views.py
│   └── products/          # Products app
│       ├── __init__.py
│       ├── admin.py
│       ├── apps.py
│       ├── migrations/
│       ├── models.py
│       ├── tests.py
│       ├── urls.py
│       └── views.py
├── templates/             # Global templates
│   └── base.html
├── static/                # Global static files
├── media/                 # User-uploaded files
├── requirements/
│   ├── base.txt
│   ├── dev.txt
│   └── prod.txt
└── README.md

Key Differences:

FastAPI

FastAPI works well with modern, modular structures:

my_fastapi_app/
├── app/
│   ├── __init__.py
│   ├── main.py            # FastAPI application creation and configuration
│   ├── core/              # Core functionality
│   │   ├── __init__.py
│   │   ├── config.py      # Settings using Pydantic
│   │   ├── security.py    # Authentication and permissions
│   │   └── database.py    # Database connection
│   ├── api/               # API endpoints
│   │   ├── __init__.py
│   │   ├── dependencies.py # Shared dependencies
│   │   ├── v1/            # API version 1
│   │   │   ├── __init__.py
│   │   │   ├── endpoints/ # Grouped by resource
│   │   │   │   ├── users.py
│   │   │   │   └── items.py
│   │   │   └── router.py  # Combined router
│   ├── models/            # SQLAlchemy models
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── item.py
│   ├── schemas/           # Pydantic schemas/models
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── item.py
│   ├── crud/              # CRUD operations
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── item.py
│   └── services/          # Business logic
│       ├── __init__.py
│       └── user_service.py
├── tests/
├── alembic/               # Migrations
├── .env                   # Environment variables
├── requirements.txt
└── README.md

Key Features:

Real-world Example: The FastAPI official project generator (fastapi-users) follows a similar structure to make it easy to get started with best practices.

Making Structure Decisions

Choosing the right structure depends on various factors:

Factors to Consider

Decision Framework

Use this simplified framework to decide on structure:

Project Type Recommended Structure
Simple web app, single developer Simple Flask Application
Medium app, small team Package-Based Structure
Complex app, multiple developers Modular Blueprint Structure
Enterprise app, complex domains Domain-Driven Design or Hexagonal Architecture

Metaphor: Think of project structure like clothing. A simple t-shirt (simple structure) works for a quick errand, but you'll need a specialized outfit (complex structure) for extreme mountain climbing. Choose the structure that fits your project's complexity and needs.

Evolution, Not Revolution

It's important to recognize that project structures evolve:

Real-world Example: Flask itself started as a simple application and evolved into a blueprint structure as its complexity grew. Many successful web applications follow this pattern of progressive organization.

Best Practices for Project Structure

Regardless of the specific structure you choose, follow these principles:

Separation of Concerns

Keep different types of functionality in separate modules:

Analogy: Like keeping ingredients, cooking utensils, and dining plates in different kitchen cabinets - it makes everything easier to find and use.

Don't Repeat Yourself (DRY)

Organize code to minimize duplication:

# myapp/core/utils.py
def paginate_query(query, page, per_page):
    """Paginate a SQLAlchemy query."""
    return query.paginate(page=page, per_page=per_page, error_out=False)

# In multiple views
from myapp.core.utils import paginate_query

# Later in a view
users = paginate_query(User.query, page, 20)

Consistency is Key

Naming Conventions Example:

Configuration Management

# .env (not in version control)
SECRET_KEY=your-very-secret-key
DATABASE_URL=postgresql://user:pass@localhost/dbname

# Using the config
import os
from dotenv import load_dotenv

load_dotenv()  # Load environment variables from .env

SECRET_KEY = os.environ.get('SECRET_KEY')
DATABASE_URL = os.environ.get('DATABASE_URL')

Configuration vs. Code: What Goes Where

Clear guidelines on what should be configuration vs. code:

Configuration (config.py, .env) Code (Python modules)
Database connection strings Database model definitions
API keys and secrets API client implementation
Feature flags Feature implementation
Environment-specific settings Application logic
Logging levels and destinations Logging setup code

Project Structure in a Team Environment

When working with teams, project structure becomes even more important:

Documentation

Document your structure for team members:

# README.md section on project structure
## Project Structure

This project follows a modular blueprint structure:

- `myapp/`: Main package
  - `__init__.py`: Application factory
  - `auth/`: Authentication module
  - `admin/`: Admin panel module
  - `public/`: Public-facing pages
  - `models/`: Database models
  - `static/`: Static assets
  - `templates/`: HTML templates
- `migrations/`: Database migrations
- `tests/`: Test suite
- `config.py`: Configuration classes

### Module Structure

Each module (auth, admin, public) follows this structure:
- `__init__.py`: Blueprint creation
- `routes.py`: View functions and routes
- `forms.py`: WTForms definitions
- `templates/`: Module-specific templates

Onboarding

Make it easy for new team members to understand the structure:

Contribution Guidelines

Set clear rules for maintaining the structure:

# CONTRIBUTING.md section
## Code Organization

- New features should be implemented as blueprints in their own module
- Keep models in the `models/` directory, one model per file
- Place shared utilities in `core/utils.py`
- Add tests for all new functionality
- Follow existing naming conventions
- Update documentation when changing structure

Code Reviews

Use code reviews to enforce structural consistency:

Practical Example: Refactoring for Better Structure

Let's look at a practical example of refactoring a simple Flask application into a better structure.

Starting Point: Simple Flask App

# app.py - Everything in one file
from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password_hash = db.Column(db.String(128))
    
    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
        
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        
        user = User.query.filter_by(username=username).first()
        if user and user.check_password(password):
            login_user(user)
            return redirect(url_for('index'))
        flash('Invalid username or password')
    return render_template('login.html')

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('index'))

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        
        if User.query.filter_by(username=username).first():
            flash('Username already exists')
        else:
            user = User(username=username)
            user.set_password(password)
            db.session.add(user)
            db.session.commit()
            flash('Registration successful')
            return redirect(url_for('login'))
    return render_template('register.html')

if __name__ == '__main__':
    db.create_all()
    app.run(debug=True)

Refactored Structure: Package-Based

Reorganize into a proper package structure:

my_flask_app/
├── myapp/
│   ├── __init__.py
│   ├── models.py
│   ├── auth.py
│   ├── routes.py
│   ├── templates/
│   │   ├── base.html
│   │   ├── index.html
│   │   ├── login.html
│   │   └── register.html
│   └── static/
├── config.py
├── requirements.txt
└── run.py
# config.py
import os

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key')
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', 'sqlite:///app.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

# myapp/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager

from config import Config

db = SQLAlchemy()
login_manager = LoginManager()
login_manager.login_view = 'auth.login'

def create_app(config_class=Config):
    app = Flask(__name__)
    app.config.from_object(config_class)
    
    db.init_app(app)
    login_manager.init_app(app)
    
    from myapp import models
    
    from myapp.auth import auth_bp
    app.register_blueprint(auth_bp)
    
    from myapp.routes import main_bp
    app.register_blueprint(main_bp)
    
    return app

# myapp/models.py
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

from myapp import db, login_manager

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password_hash = db.Column(db.String(128))
    
    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
        
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

# myapp/auth.py
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required

from myapp import db
from myapp.models import User

auth_bp = Blueprint('auth', __name__)

@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        
        user = User.query.filter_by(username=username).first()
        if user and user.check_password(password):
            login_user(user)
            return redirect(url_for('main.index'))
        flash('Invalid username or password')
    return render_template('login.html')

@auth_bp.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('main.index'))

@auth_bp.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        
        if User.query.filter_by(username=username).first():
            flash('Username already exists')
        else:
            user = User(username=username)
            user.set_password(password)
            db.session.add(user)
            db.session.commit()
            flash('Registration successful')
            return redirect(url_for('auth.login'))
    return render_template('register.html')

# myapp/routes.py
from flask import Blueprint, render_template

main_bp = Blueprint('main', __name__)

@main_bp.route('/')
def index():
    return render_template('index.html')

# run.py
from myapp import create_app, db

app = create_app()

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

Key Improvements

Result: The application is now more maintainable, testable, and easier to extend with new features.

Further Evolution: Moving to Blueprints

As the application grows, you might refactor further to a modular blueprint structure:

my_flask_app/
├── myapp/
│   ├── __init__.py
│   ├── extensions.py
│   ├── models/
│   │   ├── __init__.py
│   │   └── user.py
│   ├── auth/
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   ├── forms.py
│   │   └── templates/
│   │       └── auth/
│   │           ├── login.html
│   │           └── register.html
│   ├── main/
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   └── templates/
│   │       └── main/
│   │           └── index.html
│   ├── templates/
│   │   └── base.html
│   └── static/
├── migrations/
├── tests/
├── config.py
├── requirements.txt
└── run.py

With this structure, each feature area becomes fully encapsulated with its own routes, forms, and templates.

Conclusion

A well-designed project structure is a fundamental building block of successful web applications. As we've explored:

As we move forward in our web development journey, the structures we've discussed today will serve as templates for organizing our applications. In the coming weeks, as we dive into Flask, Django, and other frameworks, we'll see these patterns in action and understand how they contribute to building maintainable, scalable web applications.

Final Analogy: Think of project structure as architecture. A well-designed building isn't just about aesthetics—it's about creating spaces that people can navigate intuitively, that can withstand time and changing needs, and that make the intended activities efficient and pleasant. A good project structure serves the same purpose for your code and the developers who work with it.

Additional Resources