BLOG POSTS
Create a Web Application Using Flask in Python 3

Create a Web Application Using Flask in Python 3

Flask is a lightweight, flexible web framework for Python 3 that gets you from zero to functional web application faster than you can say “Hello, World!” Whether you’re building a simple API endpoint or a full-featured web application, Flask’s minimalist approach and extensive ecosystem make it an excellent choice for developers who want control without complexity. In this guide, you’ll learn how to set up Flask from scratch, build a complete web application with database integration, handle common issues that’ll inevitably pop up, and deploy your creation to a production server.

How Flask Works Under the Hood

Flask operates on a simple principle: it’s a WSGI (Web Server Gateway Interface) application that sits between your Python code and the web server. When a request comes in, Flask’s routing system matches the URL to a specific function, executes that function, and returns the response. The magic happens through decorators that bind URLs to Python functions.

At its core, Flask uses Werkzeug for WSGI utilities and Jinja2 for templating. This lightweight foundation means you’re not carrying unnecessary baggage, but you can easily add components like SQLAlchemy for database ORM, Flask-Login for authentication, or Flask-WTF for form handling when you need them.

from flask import Flask

app = Flask(__name__)

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

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

This basic structure creates a Flask application instance, defines a route, and starts a development server. The decorator @app.route('/') tells Flask to execute the hello() function when someone visits the root URL.

Setting Up Your Development Environment

Before diving into code, you’ll want a clean Python environment. Virtual environments are non-negotiable here – they prevent dependency conflicts that’ll haunt you later.

# Create a virtual environment
python3 -m venv flask_env

# Activate it (Linux/Mac)
source flask_env/bin/activate

# Activate it (Windows)
flask_env\Scripts\activate

# Install Flask and essential packages
pip install Flask Flask-SQLAlchemy Flask-WTF python-dotenv

Create your project structure like this:

my_flask_app/
├── app.py
├── config.py
├── requirements.txt
├── templates/
│   ├── base.html
│   ├── index.html
│   └── form.html
├── static/
│   ├── css/
│   └── js/
└── instance/
    └── config.py

Building a Complete Web Application

Let’s build a task management application that demonstrates real-world Flask concepts. This example includes database integration, forms, and user input handling.

First, set up the main application file:

# app.py
from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired
import os
from datetime import datetime

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

db = SQLAlchemy(app)

# Database Model
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, nullable=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    completed = db.Column(db.Boolean, default=False)

    def __repr__(self):
        return f''

# Form Class
class TaskForm(FlaskForm):
    title = StringField('Task Title', validators=[DataRequired()])
    description = TextAreaField('Description')
    submit = SubmitField('Add Task')

# Routes
@app.route('/')
def index():
    tasks = Task.query.all()
    return render_template('index.html', tasks=tasks)

@app.route('/add', methods=['GET', 'POST'])
def add_task():
    form = TaskForm()
    if form.validate_on_submit():
        task = Task(title=form.title.data, description=form.description.data)
        db.session.add(task)
        db.session.commit()
        flash('Task added successfully!', 'success')
        return redirect(url_for('index'))
    return render_template('form.html', form=form)

@app.route('/complete/')
def complete_task(task_id):
    task = Task.query.get_or_404(task_id)
    task.completed = True
    db.session.commit()
    flash('Task completed!', 'success')
    return redirect(url_for('index'))

@app.route('/delete/')
def delete_task(task_id):
    task = Task.query.get_or_404(task_id)
    db.session.delete(task)
    db.session.commit()
    flash('Task deleted!', 'danger')
    return redirect(url_for('index'))

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

Create the base template for consistent styling:

<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Task Manager{% endblock %}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container mt-4">
        {% with messages = get_flashed_messages(with_categories=true) %}
            {% if messages %}
                {% for category, message in messages %}
                    <div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
                        {{ message }}
                        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                    </div>
                {% endfor %}
            {% endif %}
        {% endwith %}
        
        {% block content %}{% endblock %}
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

The main page template:

<!-- templates/index.html -->
{% extends "base.html" %}

{% block content %}
<h1>Task Manager</h1>

<a href="{{ url_for('add_task') }}" class="btn btn-primary mb-3">Add New Task</a>

{% if tasks %}
    <div class="row">
        {% for task in tasks %}
            <div class="col-md-6 mb-3">
                <div class="card {% if task.completed %}bg-light{% endif %}">
                    <div class="card-body">
                        <h5 class="card-title {% if task.completed %}text-muted text-decoration-line-through{% endif %}">
                            {{ task.title }}
                        </h5>
                        {% if task.description %}
                            <p class="card-text">{{ task.description }}</p>
                        {% endif %}
                        <small class="text-muted">Created: {{ task.created_at.strftime('%Y-%m-%d %H:%M') }}</small>
                        <div class="mt-2">
                            {% if not task.completed %}
                                <a href="{{ url_for('complete_task', task_id=task.id) }}" class="btn btn-success btn-sm">Complete</a>
                            {% endif %}
                            <a href="{{ url_for('delete_task', task_id=task.id) }}" class="btn btn-danger btn-sm" 
                               onclick="return confirm('Are you sure you want to delete this task?')">Delete</a>
                        </div>
                    </div>
                </div>
            </div>
        {% endfor %}
    </div>
{% else %}
    <div class="alert alert-info">
        No tasks yet. <a href="{{ url_for('add_task') }}">Add your first task!</a>
    </div>
{% endif %}
{% endblock %}

Flask vs. Other Python Web Frameworks

Understanding where Flask fits in the Python web framework landscape helps you make informed decisions for your projects.

Feature Flask Django FastAPI Tornado
Learning Curve Easy Steep Moderate Moderate
Project Structure Flexible Opinionated Flexible Flexible
Built-in ORM No (SQLAlchemy) Yes No No
Admin Interface Third-party Built-in Third-party Third-party
Async Support Limited Yes (3.1+) Native Native
Performance Good Good Excellent Excellent
Community Size Large Largest Growing Moderate

Flask shines when you need flexibility and don’t want framework assumptions dictating your application structure. Choose Flask for:

  • Microservices and API development
  • Prototyping and small to medium applications
  • Learning web development concepts
  • Projects requiring custom architecture
  • Integration with existing systems

Real-World Use Cases and Examples

Flask powers more applications than you might realize. Here are some practical scenarios where Flask excels:

RESTful API Development: Flask’s lightweight nature makes it perfect for building APIs. Here’s a quick example of a JSON API endpoint:

from flask import Flask, jsonify, request

app = Flask(__name__)

# Sample data
users = [
    {'id': 1, 'name': 'John Doe', 'email': 'john@example.com'},
    {'id': 2, 'name': 'Jane Smith', 'email': 'jane@example.com'}
]

@app.route('/api/users', methods=['GET'])
def get_users():
    return jsonify({'users': users})

@app.route('/api/users/', methods=['GET'])
def get_user(user_id):
    user = next((u for u in users if u['id'] == user_id), None)
    if user:
        return jsonify({'user': user})
    return jsonify({'error': 'User not found'}), 404

@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    new_user = {
        'id': len(users) + 1,
        'name': data.get('name'),
        'email': data.get('email')
    }
    users.append(new_user)
    return jsonify({'user': new_user}), 201

Microservices Architecture: Companies like Netflix and Pinterest use Flask for microservices because of its minimal overhead and fast startup time. A typical microservice might handle user authentication:

from flask import Flask, request, jsonify
import jwt
import datetime
from functools import wraps

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-jwt-secret'

def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get('Authorization')
        if not token:
            return jsonify({'message': 'Token missing'}), 401
        try:
            token = token.split(' ')[1]  # Remove 'Bearer '
            data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
        except:
            return jsonify({'message': 'Token invalid'}), 401
        return f(*args, **kwargs)
    return decorated

@app.route('/auth/login', methods=['POST'])
def login():
    data = request.get_json()
    # Verify credentials (simplified)
    if data.get('username') == 'admin' and data.get('password') == 'secret':
        token = jwt.encode({
            'user': data.get('username'),
            'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)
        }, app.config['SECRET_KEY'], algorithm='HS256')
        return jsonify({'token': token})
    return jsonify({'message': 'Invalid credentials'}), 401

@app.route('/auth/verify', methods=['GET'])
@token_required
def verify():
    return jsonify({'message': 'Token valid', 'status': 'authenticated'})

Common Pitfalls and Troubleshooting

Every Flask developer runs into these issues. Here’s how to handle the most common ones:

Template Not Found Errors: Flask looks for templates in a templates folder by default. If you’re getting TemplateNotFound errors, check your folder structure:

# Wrong - templates in wrong location
my_app.py
login.html

# Correct structure
my_app.py
templates/
    login.html

Database Connection Issues: SQLAlchemy connection problems often stem from incorrect database URIs or missing database initialization:

# Common mistake - not creating tables
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
db = SQLAlchemy(app)

# Fix - ensure tables are created
if __name__ == '__main__':
    with app.app_context():
        db.create_all()  # This line is crucial
    app.run(debug=True)

Static Files Not Loading: CSS and JavaScript files should go in a static folder, and you need to use url_for() to reference them:

<!-- Wrong -->
<link rel="stylesheet" href="/css/style.css">

<!-- Correct -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">

CSRF Token Errors with Forms: Flask-WTF requires CSRF tokens for security. Make sure your forms include them:

<form method="POST">
    {{ form.csrf_token }}
    {{ form.title.label }} {{ form.title() }}
    {{ form.submit() }}
</form>

Session Data Not Persisting: Sessions require a secret key. Without it, session data disappears between requests:

app = Flask(__name__)
app.secret_key = 'your-secret-key-here'  # Essential for sessions

Best Practices and Security Considerations

Production Flask applications require attention to security and performance details that development servers ignore.

Environment Configuration: Never hardcode sensitive information. Use environment variables and configuration files:

# config.py
import os
from dotenv import load_dotenv

load_dotenv()

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

class ProductionConfig(Config):
    DEBUG = False

class DevelopmentConfig(Config):
    DEBUG = True

config = {
    'development': DevelopmentConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}

Input Validation and Sanitization: Always validate user input, even with form libraries:

from wtforms.validators import DataRequired, Length, Email
from markupsafe import escape

class UserForm(FlaskForm):
    username = StringField('Username', validators=[
        DataRequired(), 
        Length(min=3, max=20)
    ])
    email = StringField('Email', validators=[DataRequired(), Email()])

@app.route('/profile', methods=['POST'])
def update_profile():
    username = escape(request.form.get('username', ''))  # Escape HTML
    # Process sanitized input

Database Connection Pooling: For production applications, configure proper database connection pooling:

app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
    'pool_size': 10,
    'pool_recycle': 120,
    'pool_pre_ping': True
}

Error Handling: Implement custom error pages and logging:

import logging
from flask import render_template

@app.errorhandler(404)
def not_found(error):
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_error(error):
    app.logger.error(f'Server Error: {error}')
    return render_template('500.html'), 500

# Configure logging
if not app.debug:
    logging.basicConfig(level=logging.INFO)
    app.logger.setLevel(logging.INFO)

Deployment and Production Considerations

The Flask development server isn’t suitable for production. Here’s how to deploy properly using Gunicorn and Nginx:

# requirements.txt
Flask==2.3.3
Flask-SQLAlchemy==3.0.5
Flask-WTF==1.1.1
gunicorn==21.2.0
python-dotenv==1.0.0

# Install production requirements
pip install -r requirements.txt

# Run with Gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 app:app

Create a systemd service file for automatic startup:

# /etc/systemd/system/flask-app.service
[Unit]
Description=Flask App
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/path/to/your/app
Environment="PATH=/path/to/your/venv/bin"
ExecStart=/path/to/your/venv/bin/gunicorn -w 4 -b 127.0.0.1:8000 app:app
Restart=always

[Install]
WantedBy=multi-user.target

Configure Nginx as a reverse proxy:

# /etc/nginx/sites-available/flask-app
server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /static {
        alias /path/to/your/app/static;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Performance monitoring becomes crucial in production. Consider integrating application performance monitoring tools and implementing caching strategies with Flask-Caching for frequently accessed data.

Flask’s ecosystem provides solutions for almost every web development need, from simple scripts to complex enterprise applications. The framework’s philosophy of being a “microframework” means you start small and add only what you need, making it an excellent choice for developers who prefer explicit over implicit and flexibility over convention. For comprehensive documentation and advanced features, check the official Flask documentation.



This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.

This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.

Leave a reply

Your email address will not be published. Required fields are marked