
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.