Module 07: Flask Full-Stack Application
Learning Focus: Production Flask apps, MongoDB integration, authentication, and deployment
Table of Contents
Module Overview
What We're Building
A production-ready Flask application with:
- MongoDB database integration
 - User authentication & authorization
 - RESTful API endpoints
 - Security best practices
 - Deployment configuration (Heroku)
 
Current Status
⚠️ Note: This module has dependency compatibility issues discovered during the 2025 audit:
ImportError: cannot import name 'JSONEncoder' from 'flask.json'
Root cause: flask-mongoengine 1.0.0 is
incompatible with Flask 3.x
Workaround: Use Flask 2.x or wait for flask-mongoengine update
Architecture Overview
Project Structure
07-flask/
├── app/
│   ├── __init__.py           # App factory
│   ├── models.py             # Database models
│   ├── routes.py             # Route handlers
│   ├── routes_test.py        # Route tests
│   ├── forms.py              # WTForms
│   ├── templates/            # Jinja2 templates
│   └── static/               # CSS, JS, images
├── models/
│   └── ...                   # Additional models
├── config.py                 # Configuration
├── main.py                   # Entry point
├── requirements.txt          # Dependencies
├── .flaskenv                 # Environment variables
├── Procfile                  # Heroku deployment
└── README.md
Technology Stack
Frontend:
  └── Jinja2 templates
  └── HTML/CSS/JavaScript
Backend:
  └── Flask 3.x (Python web framework)
  └── Flask-Login (authentication)
  └── Flask-WTF (forms)
  └── Flask-CORS (cross-origin)
Database:
  └── MongoDB (NoSQL database)
  └── MongoEngine (ODM - Object Document Mapper)
Deployment:
  └── Heroku (PaaS platform)
  └── Gunicorn (WSGI server)
Core Concepts
1. Application Factory Pattern
Problem: Direct app instantiation makes testing difficult
# ❌ BAD: Global app instance
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
    return 'Hello'
if __name__ == '__main__':
    app.run()
Solution: Factory function creates app
# ✅ GOOD: Factory pattern (app/__init__.py)
from flask import Flask
from flask_mongoengine import MongoEngine
db = MongoEngine()
def create_app(config_name='default'):
    """
    Application factory function
    
    Args:
        config_name: Configuration to use (dev, test, prod)
    
    Returns:
        Configured Flask app
    """
    app = Flask(__name__)
    
    # Load configuration
    app.config.from_object(f'config.{config_name}')
    
    # Initialize extensions
    db.init_app(app)
    
    # Register blueprints
    from .routes import main_bp
    app.register_blueprint(main_bp)
    
    return app
Benefits:
- Multiple app instances for testing
 - Different configs (dev, test, prod)
 - Delayed initialization
 - Better separation of concerns
 
2. MongoDB & MongoEngine
Traditional SQL (PostgreSQL, MySQL):
-- Structured tables
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100)
);
-- Rigid schema
INSERT INTO users (username, email) VALUES ('brennan', 'b@example.com');
NoSQL MongoDB:
// Flexible documents
{
    "_id": ObjectId("..."),
    "username": "brennan",
    "email": "b@example.com",
    "profile": {
        "age": 25,
        "city": "Calgary"
    },
    "tags": ["developer", "student"]
}
// No fixed schema!
MongoEngine ODM:
from mongoengine import Document, StringField, IntField
class User(Document):
    """
    User model - maps to MongoDB collection
    """
    username = StringField(required=True, unique=True, max_length=50)
    email = StringField(required=True, unique=True)
    password_hash = StringField(required=True)
    
    # OOP methods
    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)
# Usage
user = User(username='brennan', email='b@example.com')
user.set_password('secret123')
user.save()  # Saves to MongoDB
# Query
user = User.objects(username='brennan').first()
3. Authentication with Flask-Login
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required
# Setup
login_manager = LoginManager()
login_manager.init_app(app)
# User loader callback
@login_manager.user_loader
def load_user(user_id):
    return User.objects(id=user_id).first()
# Login route
@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')
    
    user = User.objects(username=username).first()
    
    if user and user.check_password(password):
        login_user(user)  # Creates session
        return redirect('/dashboard')
    
    return 'Invalid credentials', 401
# Protected route
@app.route('/dashboard')
@login_required  # Decorator requires authentication
def dashboard():
    return f'Welcome {current_user.username}'
# Logout
@app.route('/logout')
@login_required
def logout():
    logout_user()  # Destroys session
    return redirect('/')
4. Environment Configuration
# config.py
import os
class Config:
    """Base configuration"""
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
    MONGODB_SETTINGS = {
        'db': 'myapp',
        'host': 'localhost',
        'port': 27017
    }
class DevelopmentConfig(Config):
    """Development configuration"""
    DEBUG = True
    TESTING = False
class TestingConfig(Config):
    """Testing configuration"""
    DEBUG = False
    TESTING = True
    MONGODB_SETTINGS = {
        'db': 'myapp_test',
        'host': 'localhost',
        'port': 27017
    }
class ProductionConfig(Config):
    """Production configuration"""
    DEBUG = False
    TESTING = False
    MONGODB_SETTINGS = {
        'db': os.environ.get('MONGODB_DB'),
        'host': os.environ.get('MONGODB_URI'),
        'username': os.environ.get('MONGODB_USER'),
        'password': os.environ.get('MONGODB_PASSWORD')
    }
# Map config names to classes
config = {
    'development': DevelopmentConfig,
    'testing': TestingConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}
Code Examples
models.py - Database Models
from app import db
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Document):
    """User model"""
    user_id = db.IntField(required=True, unique=True)
    first_name = db.StringField(max_length=50)
    last_name = db.StringField(max_length=50)
    email = db.StringField(required=True, unique=True, max_length=100)
    password = db.StringField(required=True)
    created_at = db.DateTimeField(default=datetime.utcnow)
    
    meta = {
        'collection': 'users',
        'indexes': ['user_id', 'email']
    }
    
    def set_password(self, password):
        """Hash and store password"""
        self.password = generate_password_hash(password)
    
    def check_password(self, password):
        """Verify password"""
        return check_password_hash(self.password, password)
    
    def __repr__(self):
        return f'<User {self.email}>'
class Course(db.Document):
    """Course model"""
    course_id = db.IntField(required=True, unique=True)
    course_name = db.StringField(required=True, max_length=100)
    instructor = db.StringField(max_length=100)
    credits = db.IntField(min_value=1, max_value=10)
    
    meta = {
        'collection': 'courses',
        'indexes': ['course_id']
    }
    
    def __repr__(self):
        return f'<Course {self.course_name}>'
class Enrollment(db.Document):
    """Enrollment (Many-to-Many relationship)"""
    user = db.ReferenceField(User, required=True)
    course = db.ReferenceField(Course, required=True)
    enrolled_at = db.DateTimeField(default=datetime.utcnow)
    grade = db.StringField(max_length=2)
    
    meta = {
        'collection': 'enrollments',
        'indexes': [
            ('user', 'course')  # Compound index
        ]
    }
    
    def __repr__(self):
        return f'<Enrollment {self.user.email} - {self.course.course_name}>'
routes.py - API Endpoints
from flask import Blueprint, request, jsonify
from app.models import User, Course, Enrollment
from flask_login import login_required, current_user
main_bp = Blueprint('main', __name__)
def test_function():
    """Test function for basic route testing"""
    return "Success!"
@main_bp.route('/api/users', methods=['GET'])
def get_users():
    """Get all users"""
    users = User.objects.all()
    return jsonify([{
        'user_id': u.user_id,
        'first_name': u.first_name,
        'last_name': u.last_name,
        'email': u.email
    } for u in users]), 200
@main_bp.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    """Get specific user"""
    user = User.objects(user_id=user_id).first()
    
    if not user:
        return jsonify({'error': 'User not found'}), 404
    
    return jsonify({
        'user_id': user.user_id,
        'first_name': user.first_name,
        'last_name': user.last_name,
        'email': user.email
    }), 200
@main_bp.route('/api/users', methods=['POST'])
def create_user():
    """Create new user"""
    data = request.get_json()
    
    # Validation
    required = ['user_id', 'first_name', 'last_name', 'email', 'password']
    if not all(field in data for field in required):
        return jsonify({'error': 'Missing required fields'}), 400
    
    # Check if user exists
    if User.objects(email=data['email']).first():
        return jsonify({'error': 'Email already exists'}), 400
    
    # Create user
    user = User(
        user_id=data['user_id'],
        first_name=data['first_name'],
        last_name=data['last_name'],
        email=data['email']
    )
    user.set_password(data['password'])
    user.save()
    
    return jsonify({'message': 'User created', 'user_id': user.user_id}), 201
@main_bp.route('/api/enroll', methods=['POST'])
@login_required  # Must be logged in
def enroll():
    """Enroll user in course"""
    data = request.get_json()
    
    course_id = data.get('course_id')
    course = Course.objects(course_id=course_id).first()
    
    if not course:
        return jsonify({'error': 'Course not found'}), 404
    
    # Check if already enrolled
    existing = Enrollment.objects(
        user=current_user.id,
        course=course.id
    ).first()
    
    if existing:
        return jsonify({'error': 'Already enrolled'}), 400
    
    # Create enrollment
    enrollment = Enrollment(user=current_user.id, course=course.id)
    enrollment.save()
    
    return jsonify({'message': 'Enrolled successfully'}), 201
routes_test.py - Testing
from app import routes
from app.models import User, Course, Enrollment
import pytest
def test_canRoutesBeCalled():
    """Test basic route functionality"""
    output = routes.test_function()
    assert output == "Success!"
def mock_users():
    """Create mock users for testing"""
    User(
        user_id=1,
        first_name="Jane",
        last_name="Testname",
        email="jane_testname@flaskschool.com",
        password="fake_password1"
    ).save()
    
    User(
        user_id=2,
        first_name="John",
        last_name="Testname",
        email="john_testname@flaskschool.com",
        password="fake_password2"
    ).save()
# More tests would go here...
Challenges & Learnings
Challenge 1: Dependency Compatibility
Problem: Flask 3.x changed internal structure, breaking flask-mongoengine
# flask-mongoengine tries to import:
from flask.json import JSONEncoder  # Doesn't exist in Flask 3.x!
Solutions:
- Downgrade to Flask 2.x
 - Wait for flask-mongoengine update
 - Switch to PyMongo directly
 - Use SQLAlchemy instead
 
Lesson: Always check dependency compatibility in
requirements.txt
Challenge 2: MongoDB Setup
Problem: MongoDB must be running locally or in cloud
# Install MongoDB
brew install mongodb-community
# Start MongoDB
brew services start mongodb-community
# Verify it's running
mongo --eval "db.stats()"
Cloud Alternative: MongoDB Atlas (free tier)
Challenge 3: Environment Variables
Problem: Secrets shouldn't be in code
# ❌ BAD: Hardcoded secrets
MONGODB_URI = "mongodb://user:password@host:27017/db"
SECRET_KEY = "my-secret-key-123"
# ✅ GOOD: Use environment variables
MONGODB_URI = os.environ.get('MONGODB_URI')
SECRET_KEY = os.environ.get('SECRET_KEY')
Solution: Use .env file (don't
commit!)
# .env (add to .gitignore!)
MONGODB_URI=mongodb://localhost:27017/myapp
SECRET_KEY=your-secret-key-here
FLASK_ENV=development
Key Takeaways
Production Checklist
Best Practices
1. Blueprints for Organization
# Organize routes by feature
app/
├── auth/              # Authentication routes
│   └── routes.py
├── api/               # API routes
│   └── routes.py
└── admin/             # Admin routes
    └── routes.py
2. Database Indexes
class User(db.Document):
    email = db.StringField(required=True, unique=True)
    
    meta = {
        'indexes': [
            'email',  # Single field index
            ('first_name', 'last_name')  # Compound index
        ]
    }
3. Error Handling
@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': 'Not found'}), 404
@app.errorhandler(500)
def internal_error(error):
    db.session.rollback()  # Rollback any failed transactions
    return jsonify({'error': 'Internal server error'}), 500
Security Considerations
- Password Hashing: Never store plain passwords
 - CSRF Protection: Use Flask-WTF
 - SQL Injection: Use parameterized queries (ORM does this)
 - XSS: Escape user input
 - Rate Limiting: Prevent brute force
 - HTTPS: Encrypt data in transit
 
Further Learning
Next Steps
- Complete Module: Fix dependency issues, run tests
 - Add Features: User profiles, password reset, email verification
 - Deploy: Heroku, AWS, or DigitalOcean
 - Scale: Add caching (Redis), load balancing
 
Resources
Real-World Applications
- SaaS Platforms: User management, subscriptions
 - E-commerce: Product catalogs, orders
 - Social Networks: Posts, comments, likes
 - APIs: Public/private data access
 
Conclusion
Module 07 represents the culmination of the bootcamp—a production-grade full-stack application. While we encountered compatibility issues during the 2025 audit, the architectural patterns and concepts remain valuable:
- Application factory pattern
 - Database ORM/ODM
 - Authentication & authorization
 - RESTful API design
 - Production configuration
 - Deployment strategies
 
These patterns transfer to any web framework, whether Flask, Django, FastAPI, Express, or Rails.
Module Status: ⚠️ Blocked (dependency issues)
Key Concepts: Factory pattern, MongoDB,
authentication
Time Investment: ~4 hours (setup + learning)
Key Achievement: Understanding production-grade Flask
architecture!
🎓 Bootcamp Complete! Return to Documentation Index