Skip to content

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:

# @filename: script.sh
# 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

# @filename: Dockerfile
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)

# @filename: Dockerfile

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)

# @filename: Dockerfile
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)

# @filename: Dockerfile
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)

# @filename: Dockerfile
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)

# @filename: Dockerfile
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)

# @filename: Dockerfile
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)

# @filename: Dockerfile
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:

# @filename: script.sh
# 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

Python Programming
Share:

Continue Reading

Mastering Object-Oriented Programming in Python: A Complete Guide

Learn object-oriented programming in Python from the ground up. Master classes, inheritance, polymorphism, and encapsulation with practical examples. Perfect for developers looking to write more organized and maintainable Python code in 2024.

Read article
PythonProgramming

Python File Handling and I/O Operations: A Practical Guide

File handling is a crucial skill for any Python developer. From reading and writing text files to handling binary data and working with different file formats, this guide covers everything you need to know about file operations in Python. Learn through practical examples and build a file management utility project.

Read article
PythonProgramming