Python Web Development with Flask: Building Modern Web Applications


Python Web Development with Flask: Building Modern Web Applications

Flask is a lightweight and flexible Python web framework that's perfect for building web applications and APIs. This guide will walk you through Flask fundamentals and help you build a complete web application with authentication and database integration.

Getting Started with Flask

First, let's set up our development environment:

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install required packages
pip install flask flask-sqlalchemy flask-login flask-migrate python-dotenv

Basic Flask Application

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

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

Project: Task Management API

Let's build a complete task management application with Flask. This project will include:

  • RESTful API endpoints
  • User authentication
  • Database integration
  • Error handling
  • API documentation

Project Structure

task_manager/
├── .env
├── config.py
├── requirements.txt
├── run.py
└── app/
    ├── __init__.py
    ├── models.py
    ├── routes.py
    ├── auth.py
    ├── utils.py
    └── templates/
        ├── base.html
        ├── login.html
        └── dashboard.html

Configuration (config.py)

import os
from dotenv import load_dotenv

load_dotenv()

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'your-secret-key'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
        'sqlite:///app.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

Application Factory (app/__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()

def create_app(config_class=Config):
    app = Flask(__name__)
    app.config.from_object(config_class)
    
    # Initialize extensions
    db.init_app(app)
    login_manager.init_app(app)
    login_manager.login_view = 'auth.login'
    
    # Register blueprints
    from app.routes import main
    from app.auth import auth
    app.register_blueprint(main)
    app.register_blueprint(auth)
    
    return app

Database Models (app/models.py)

from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
from app import db, login_manager

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

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(128))
    tasks = db.relationship('Task', backref='author', lazy='dynamic')
    
    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)

class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    description = db.Column(db.Text)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    due_date = db.Column(db.DateTime)
    completed = db.Column(db.Boolean, default=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    
    def to_dict(self):
        return {
            'id': self.id,
            'title': self.title,
            'description': self.description,
            'created_at': self.created_at.isoformat(),
            'due_date': self.due_date.isoformat() if self.due_date else None,
            'completed': self.completed
        }

Authentication Routes (app/auth.py)

from flask import Blueprint, request, jsonify
from flask_login import login_user, logout_user, login_required
from app.models import User, db

auth = Blueprint('auth', __name__)

@auth.route('/register', methods=['POST'])
def register():
    data = request.get_json()
    
    if User.query.filter_by(username=data['username']).first():
        return jsonify({'error': 'Username already exists'}), 400
    
    if User.query.filter_by(email=data['email']).first():
        return jsonify({'error': 'Email already registered'}), 400
    
    user = User(username=data['username'], email=data['email'])
    user.set_password(data['password'])
    
    db.session.add(user)
    db.session.commit()
    
    return jsonify({'message': 'User registered successfully'}), 201

@auth.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    user = User.query.filter_by(username=data['username']).first()
    
    if user and user.check_password(data['password']):
        login_user(user)
        return jsonify({'message': 'Logged in successfully'})
    
    return jsonify({'error': 'Invalid username or password'}), 401

@auth.route('/logout')
@login_required
def logout():
    logout_user()
    return jsonify({'message': 'Logged out successfully'})

Task Routes (app/routes.py)

from flask import Blueprint, request, jsonify
from flask_login import login_required, current_user
from app.models import Task, db
from datetime import datetime

main = Blueprint('main', __name__)

@main.route('/tasks', methods=['GET'])
@login_required
def get_tasks():
    tasks = current_user.tasks.all()
    return jsonify([task.to_dict() for task in tasks])

@main.route('/tasks', methods=['POST'])
@login_required
def create_task():
    data = request.get_json()
    
    task = Task(
        title=data['title'],
        description=data.get('description'),
        due_date=datetime.fromisoformat(data['due_date']) if data.get('due_date') else None,
        author=current_user
    )
    
    db.session.add(task)
    db.session.commit()
    
    return jsonify(task.to_dict()), 201

@main.route('/tasks/<int:task_id>', methods=['PUT'])
@login_required
def update_task(task_id):
    task = Task.query.get_or_404(task_id)
    
    if task.user_id != current_user.id:
        return jsonify({'error': 'Unauthorized'}), 403
    
    data = request.get_json()
    task.title = data.get('title', task.title)
    task.description = data.get('description', task.description)
    task.completed = data.get('completed', task.completed)
    
    if data.get('due_date'):
        task.due_date = datetime.fromisoformat(data['due_date'])
    
    db.session.commit()
    return jsonify(task.to_dict())

@main.route('/tasks/<int:task_id>', methods=['DELETE'])
@login_required
def delete_task(task_id):
    task = Task.query.get_or_404(task_id)
    
    if task.user_id != current_user.id:
        return jsonify({'error': 'Unauthorized'}), 403
    
    db.session.delete(task)
    db.session.commit()
    return '', 204

Error Handling (app/utils.py)

from flask import jsonify
from app import create_app

app = create_app()

@app.errorhandler(404)
def not_found_error(error):
    return jsonify({'error': 'Not found'}), 404

@app.errorhandler(500)
def internal_error(error):
    db.session.rollback()
    return jsonify({'error': 'Internal server error'}), 500

Running the Application (run.py)

from app import create_app, db

app = create_app()

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

Testing the API

Here are some example API requests using curl:

# Register a new user
curl -X POST http://localhost:5000/register \
     -H "Content-Type: application/json" \
     -d '{"username":"testuser","email":"test@example.com","password":"password123"}'

# Login
curl -X POST http://localhost:5000/login \
     -H "Content-Type: application/json" \
     -d '{"username":"testuser","password":"password123"}'

# Create a task
curl -X POST http://localhost:5000/tasks \
     -H "Content-Type: application/json" \
     -d '{"title":"Test Task","description":"This is a test task","due_date":"2024-03-01T00:00:00"}'

# Get all tasks
curl http://localhost:5000/tasks

# Update a task
curl -X PUT http://localhost:5000/tasks/1 \
     -H "Content-Type: application/json" \
     -d '{"completed":true}'

# Delete a task
curl -X DELETE http://localhost:5000/tasks/1

Best Practices

  1. Security

    • Use HTTPS in production
    • Implement rate limiting
    • Sanitize user input
    • Use secure session management
  2. Performance

    • Use database indexing
    • Implement caching
    • Optimize database queries
    • Use async operations when appropriate
  3. Code Organization

    • Follow the Blueprint pattern
    • Separate concerns
    • Use configuration files
    • Implement proper error handling
  4. Testing

    • Write unit tests
    • Use integration tests
    • Test error cases
    • Use test fixtures

Deployment Considerations

  1. Environment Variables

    # .env
    SECRET_KEY=your-secret-key
    DATABASE_URL=postgresql://user:password@localhost/dbname
    FLASK_ENV=production
    
  2. Production Server

    pip install gunicorn
    gunicorn "app:create_app()"
    
  3. Database Migrations

    from flask_migrate import Migrate
    migrate = Migrate(app, db)
    
    # Create migration
    flask db init
    flask db migrate -m "Initial migration"
    flask db upgrade
    

Conclusion

Flask provides a flexible and powerful platform for building web applications. This project demonstrates:

  • RESTful API development
  • User authentication
  • Database integration
  • Error handling
  • Best practices for Flask applications

Continue exploring Flask's ecosystem and build upon this foundation to create more complex applications.


Further Reading