py4u guide

Building Web Applications with Python and Flask

In the world of web development, Python has emerged as a powerhouse, thanks to its simplicity, readability, and vast ecosystem of libraries. When it comes to building web applications with Python, **Flask** stands out as a lightweight, flexible, and beginner-friendly framework. Unlike monolithic frameworks like Django (which come "batteries-included"), Flask is a "micro-framework"—it provides the core tools needed to build web apps (routing, templating, request handling) without imposing unnecessary structure, giving developers full control over their project’s architecture. Whether you’re building a personal blog, a to-do app, a small business website, or even a prototype for a larger project, Flask’s minimalistic design and extensibility make it an excellent choice. In this blog, we’ll walk through the entire process of building a web application with Flask, from setup to deployment, with hands-on examples and best practices.

Table of Contents

  1. Introduction
  2. Setting Up Your Environment
    • 2.1 Installing Python
    • 2.2 Virtual Environments
    • 2.3 Installing Flask
  3. Your First Flask App: “Hello World”
    • 3.1 Project Structure
    • 3.2 Writing the Code
    • 3.3 Running the App
  4. Routing and URL Handling
    • 4.1 Basic Routes
    • 4.2 Dynamic URLs
    • 4.3 HTTP Methods (GET, POST)
  5. Templates with Jinja2
    • 5.1 Template Inheritance
    • 5.2 Variables, Loops, and Conditionals
  6. Serving Static Files (CSS, JS, Images)
  7. Handling Forms with Flask-WTF
    • 7.1 Installing Flask-WTF
    • 7.2 Creating a Form
    • 7.3 Rendering and Validating Forms
  8. Database Integration with SQLAlchemy
    • 8.1 Installing Flask-SQLAlchemy
    • 8.2 Defining Models
    • 8.3 CRUD Operations
  9. User Authentication with Flask-Login
    • 9.1 Setting Up Flask-Login
    • 9.2 User Registration and Login
    • 9.3 Protecting Routes
  10. Deploying Your Flask App
    • 10.1 Preparing for Deployment
    • 10.2 Deploying to Heroku
  11. Best Practices for Flask Development
  12. Conclusion
  13. References

2. Setting Up Your Environment

Before diving into Flask, let’s set up a clean development environment. This ensures dependencies are isolated and your project remains reproducible.

2.1 Installing Python

Flask requires Python 3.6 or higher. If you don’t have Python installed:

  • Windows/macOS: Download from python.org.
  • Linux: Use your package manager (e.g., sudo apt install python3 for Ubuntu).

Verify installation by running:

python --version  # or python3 --version  

2.2 Virtual Environments

A virtual environment isolates project dependencies. Use Python’s built-in venv module:

# Create a project folder  
mkdir flask-webapp && cd flask-webapp  

# Create a virtual environment  
python -m venv venv  

# Activate the virtual environment  
# On Windows (Command Prompt):  
venv\Scripts\activate  
# On Windows (PowerShell):  
.\venv\Scripts\Activate.ps1  
# On macOS/Linux:  
source venv/bin/activate  

You’ll see (venv) in your terminal prompt, indicating the environment is active.

2.3 Installing Flask

With the virtual environment active, install Flask using pip:

pip install flask  

Verify installation by running:

flask --version  

You should see output like Flask 2.3.3, Python 3.9.7.

3. Your First Flask App: “Hello World”

Let’s build a minimal Flask app to test our setup.

3.1 Project Structure

For now, our project will have a single file:

flask-webapp/  
├── venv/  
└── app.py  # Our Flask application  

3.2 Writing the Code

Create app.py with the following content:

# app.py  
from flask import Flask  

# Initialize the Flask app  
app = Flask(__name__)  

# Define a route for the homepage  
@app.route('/')  
def hello_world():  
    return "Hello, Flask!"  

# Run the app if this file is executed directly  
if __name__ == '__main__':  
    app.run(debug=True)  

3.3 Running the App

In the terminal (with venv active), run:

python app.py  

You’ll see output like:

* Serving Flask app 'app'  
* Debug mode: on  
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)  

Open http://localhost:5000 in your browser. You should see “Hello, Flask!”—congratulations, your first Flask app is running!

Note: debug=True enables auto-reloading when you edit code (use only in development, not production).

4. Routing and URL Handling

Routes map URLs to Python functions (called “view functions”). Flask uses the @app.route() decorator to define routes.

4.1 Basic Routes

Add more routes to app.py to handle different URLs:

# app.py (updated)  
from flask import Flask  

app = Flask(__name__)  

@app.route('/')  
def home():  
    return "Welcome to the Home Page!"  

@app.route('/about')  
def about():  
    return "This is the About Page."  

@app.route('/contact')  
def contact():  
    return "Contact us at [email protected]"  

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

Now visit http://localhost:5000/about or /contact to see the new pages.

4.2 Dynamic URLs

Flask supports dynamic URL parameters using <variable_name>. For example, a user profile page:

@app.route('/user/<username>')  
def user_profile(username):  
    return f"Hello, {username}!"  

Visit http://localhost:5000/user/Alice—you’ll see “Hello, Alice!“.

You can specify data types (e.g., int, float):

@app.route('/post/<int:post_id>')  
def show_post(post_id):  
    return f"Displaying Post #{post_id}"  

Now http://localhost:5000/post/42 works, but /post/abc returns a 404 error.

4.3 HTTP Methods

By default, routes accept only GET requests. To handle POST (e.g., form submissions), specify methods in @app.route():

from flask import request  # Add this import  

@app.route('/login', methods=['GET', 'POST'])  
def login():  
    if request.method == 'POST':  
        # Handle form submission  
        username = request.form['username']  
        return f"Logged in as {username} (via POST)"  
    else:  
        # Show login form (GET request)  
        return "Please submit the login form (via GET)"  

Test with curl -X POST -d "username=Alice" http://localhost:5000/login (or use a form later).

5. Templates with Jinja2

Flask uses Jinja2, a powerful templating engine, to render dynamic HTML. Templates let you separate logic (Python) from presentation (HTML).

5.1 Project Structure for Templates

Create a templates folder to store HTML files:

flask-webapp/  
├── venv/  
├── app.py  
└── templates/  
    ├── base.html       # Base template (shared across pages)  
    ├── index.html      # Homepage  
    └── about.html      # About page  

5.2 Template Inheritance

Avoid repeating HTML (e.g., headers, footers) with template inheritance. Define a base.html with common elements, then extend it in other templates.

templates/base.html:

<!DOCTYPE html>  
<html>  
<head>  
    <title>{% block title %}My Flask App{% endblock %}</title>  
</head>  
<body>  
    <header>  
        <h1>My Flask App</h1>  
        <nav>  
            <a href="/">Home</a> |  
            <a href="/about">About</a>  
        </nav>  
    </header>  
    <main>  
        {% block content %}{% endblock %}  <!-- Page-specific content here -->  
    </main>  
    <footer>  
        <p>© 2024 My Flask App</p>  
    </footer>  
</body>  
</html>  

templates/index.html (extends base.html):

{% extends "base.html" %}  

{% block title %}Home{% endblock %}  

{% block content %}  
    <h2>Welcome to the Home Page</h2>  
    <p>Today's date: {{ today }}</p>  <!-- Dynamic variable -->  
{% endblock %}  

5.3 Rendering Templates

Use render_template() to send templates to the browser. Update app.py:

from flask import render_template  # Add this import  
from datetime import datetime  # For dynamic date  

@app.route('/')  
def home():  
    today = datetime.now().strftime("%Y-%m-%d")  
    return render_template('index.html', today=today)  # Pass variables to template  

@app.route('/about')  
def about():  
    return render_template('about.html')  

templates/about.html:

{% extends "base.html" %}  

{% block title %}About{% endblock %}  

{% block content %}  
    <h2>About Us</h2>  
    <p>This is a Flask web app built with Python.</p>  
{% endblock %}  

Visit http://localhost:5000—you’ll see the dynamic date and shared header/footer!

5.4 Jinja2 Features

Jinja2 supports variables, loops, conditionals, and more:

  • Variables: {{ user.name }} (passed from Flask)
  • Loops:
    <ul>  
      {% for post in posts %}  
          <li>{{ post.title }}</li>  
      {% endfor %}  
    </ul>  
  • Conditionals:
    {% if user.is_authenticated %}  
        <p>Welcome back, {{ user.username }}!</p>  
    {% else %}  
        <p>Please log in.</p>  
    {% endif %}  

6. Serving Static Files (CSS, JS, Images)

Static files (CSS, JavaScript, images) enhance your app’s design and interactivity. Flask serves them from a static folder.

6.1 Project Structure

Add a static folder with subfolders for assets:

flask-webapp/  
├── ...  
├── static/  
│   ├── css/  
│   │   └── style.css  
│   ├── js/  
│   │   └── app.js  
│   └── img/  
│       └── logo.png  
└── templates/  
    ...  

6.2 Linking Static Files in Templates

Use url_for('static', filename='path/to/file') to link static files in Jinja2:

templates/base.html (updated head section):

<head>  
    <title>{% block title %}My Flask App{% endblock %}</title>  
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">  
    <script src="{{ url_for('static', filename='js/app.js') }}"></script>  
</head>  

6.3 Example CSS

static/css/style.css:

body {  
    font-family: Arial, sans-serif;  
    max-width: 800px;  
    margin: 0 auto;  
    padding: 20px;  
}  

header {  
    background-color: #f0f0f0;  
    padding: 10px;  
    border-radius: 5px;  
}  

nav a {  
    margin-right: 15px;  
    text-decoration: none;  
    color: #333;  
}  

nav a:hover {  
    color: #007bff;  
}  

Refresh http://localhost:5000—your app now has styling!

7. Handling Forms with Flask-WTF

Forms are critical for user input (e.g., login, registration). Flask-WTF simplifies form handling, including validation and CSRF protection.

7.1 Installing Flask-WTF

pip install flask-wtf  

7.2 Creating a Form

Define forms as Python classes. Create forms.py in your project:

# forms.py  
from flask_wtf import FlaskForm  
from wtforms import StringField, PasswordField, SubmitField  
from wtforms.validators import DataRequired, Email, Length  

class LoginForm(FlaskForm):  
    username = StringField('Username', validators=[DataRequired()])  
    password = PasswordField('Password', validators=[DataRequired(), Length(min=6)])  
    submit = SubmitField('Log In')  

class RegistrationForm(FlaskForm):  
    email = StringField('Email', validators=[DataRequired(), Email()])  
    username = StringField('Username', validators=[DataRequired(), Length(min=4, max=20)])  
    password = PasswordField('Password', validators=[DataRequired(), Length(min=6)])  
    submit = SubmitField('Register')  
  • validators ensure inputs are valid (e.g., DataRequired() = field can’t be empty).

7.3 Rendering and Validating Forms

Update app.py to handle the login form:

# app.py (updated)  
from flask import Flask, render_template, redirect, url_for, flash  
from forms import LoginForm  # Import the form  
import secrets  

app = Flask(__name__)  
app.config['SECRET_KEY'] = secrets.token_hex(16)  # Required for CSRF protection  

@app.route('/login', methods=['GET', 'POST'])  
def login():  
    form = LoginForm()  
    if form.validate_on_submit():  # Checks if form is submitted and valid  
        # Validate credentials (replace with real logic later)  
        if form.username.data == 'admin' and form.password.data == 'password':  
            flash('Logged in successfully!', 'success')  # Show message to user  
            return redirect(url_for('home'))  # Redirect to homepage  
        else:  
            flash('Invalid username or password', 'danger')  
    return render_template('login.html', form=form)  

7.4 Creating a Login Template

templates/login.html:

{% extends "base.html" %}  

{% block title %}Login{% endblock %}  

{% block content %}  
    <h2>Login</h2>  
    <form method="POST">  
        {{ form.hidden_tag() }}  <!-- Required for CSRF protection -->  

        <div>  
            {{ form.username.label }}<br>  
            {{ form.username(size=32) }}<br>  
            {% for error in form.username.errors %}  
                <span style="color: red;">{{ error }}</span>  
            {% endfor %}  
        </div>  

        <div>  
            {{ form.password.label }}<br>  
            {{ form.password(size=32) }}<br>  
            {% for error in form.password.errors %}  
                <span style="color: red;">{{ error }}</span>  
            {% endfor %}  
        </div>  

        <div>{{ form.submit() }}</div>  
    </form>  

    <!-- Display flash messages -->  
    {% with messages = get_flashed_messages(with_categories=true) %}  
        {% if messages %}  
            {% for category, message in messages %}  
                <div style="color: {% if category == 'success' %}green{% else %}red{% endif %}">  
                    {{ message }}  
                </div>  
            {% endfor %}  
        {% endif %}  
    {% endwith %}  
{% endblock %}  

Add a link to /login in base.html’s nav:

<a href="{{ url_for('login') }}">Login</a>  

Test with username “admin” and password “password”—you’ll see a success message!

8. Database Integration with SQLAlchemy

Most apps need a database to store data (users, posts, etc.). Flask-SQLAlchemy is an ORM (Object-Relational Mapper) that lets you interact with databases using Python objects instead of raw SQL.

8.1 Installing Flask-SQLAlchemy

pip install flask-sqlalchemy  

8.2 Setting Up the Database

Configure the database in app.py:

# app.py (updated)  
from flask_sqlalchemy import SQLAlchemy  

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'  # SQLite database file  
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  # Disable deprecated feature  
db = SQLAlchemy(app)  # Initialize SQLAlchemy  

8.3 Defining Models

Models are Python classes that represent database tables. Create a models.py file:

# models.py  
from datetime import datetime  
from app import db, login_manager  
from flask_login import UserMixin  

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

class User(db.Model, UserMixin):  
    id = db.Column(db.Integer, primary_key=True)  
    username = db.Column(db.String(20), unique=True, nullable=False)  
    email = db.Column(db.String(120), unique=True, nullable=False)  
    password = db.Column(db.String(60), nullable=False)  # Store hashed passwords!  
    posts = db.relationship('Post', backref='author', lazy=True)  

    def __repr__(self):  
        return f"User('{self.username}', '{self.email}')"  

class Post(db.Model):  
    id = db.Column(db.Integer, primary_key=True)  
    title = db.Column(db.String(100), nullable=False)  
    date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)  
    content = db.Column(db.Text, nullable=False)  
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)  

    def __repr__(self):  
        return f"Post('{self.title}', '{self.date_posted}')"  
  • User and Post are tables; id, username, etc., are columns.
  • posts = db.relationship('Post', ...) links users to their posts (one-to-many).

8.4 Creating the Database

In the Python shell, create tables:

python  
>>> from app import app, db  
>>> app.app_context().push()  # Required for Flask-SQLAlchemy >= 2.0  
>>> db.create_all()  # Creates tables based on models  

A site.db file will appear in your project folder.

8.5 CRUD Operations

Add, read, update, or delete data using SQLAlchemy:

# Add a user  
new_user = User(username='Alice', email='[email protected]', password='hashed_password')  
db.session.add(new_user)  
db.session.commit()  

# Query users  
user = User.query.filter_by(username='Alice').first()  
all_users = User.query.all()  

# Update a user  
user.username = 'Alicia'  
db.session.commit()  

# Delete a user  
db.session.delete(user)  
db.session.commit()  

9. User Authentication with Flask-Login

Flask-Login handles user sessions, login/logout, and route protection.

9.1 Installing Flask-Login

pip install flask-login  

9.2 Setting Up Flask-Login

Update app.py to initialize Flask-Login:

# app.py (updated)  
from flask_login import LoginManager, login_user, logout_user, login_required, current_user  

login_manager = LoginManager(app)  
login_manager.login_view = 'login'  # Redirect here if login is required  
login_manager.login_message_category = 'info'  

9.3 Protecting Routes

Use the @login_required decorator to restrict access:

@app.route('/dashboard')  
@login_required  
def dashboard():  
    return f"Welcome to your dashboard, {current_user.username}!"  

9.4 Login/Logout Logic

Update the login route to use login_user:

from werkzeug.security import check_password_hash  # For password hashing (see best practices)  

@app.route('/login', methods=['GET', 'POST'])  
def login():  
    if current_user.is_authenticated:  
        return redirect(url_for('home'))  # Redirect if already logged in  
    form = LoginForm()  
    if form.validate_on_submit():  
        user = User.query.filter_by(username=form.username.data).first()  
        if user and check_password_hash(user.password, form.password.data):  # Check hashed password  
            login_user(user)  
            return redirect(url_for('dashboard'))  
        else:  
            flash('Invalid username or password', 'danger')  
    return render_template('login.html', form=form)  

@app.route('/logout')  
def logout():  
    logout_user()  
    return redirect(url_for('home'))  

10. Deploying Your Flask App

Once your app is ready, deploy it to a production server. We’ll use Heroku (free tier available).

10.1 Preparing for Deployment

  1. Create requirements.txt (lists dependencies):

    pip freeze > requirements.txt  
  2. Create a Procfile (tells Heroku how to run the app):

    web: gunicorn app:app  
  3. Install Gunicorn (WSGI server for production):

    pip install gunicorn  

10.2 Deploying to Heroku

  1. Sign up at Heroku.com and install the Heroku CLI.

  2. Login and deploy:

    heroku login  
    heroku create my-flask-app  # Replace with your app name  
    git init  
    git add .  
    git commit -m "Initial commit"  
    git push heroku main  
    heroku open  # Opens your app in the browser  

11. Best Practices for Flask Development

  • Use Virtual Environments: Isolate dependencies.
  • Store Secrets Securely: Use environment variables (e.g., python-dotenv) instead of hardcoding SECRET_KEY.
  • Hash Passwords: Use werkzeug.security.generate_password_hash and check_password_hash.
  • Validate Input: Always validate user input to prevent attacks.
  • Use Blueprints: Organize large apps into blueprints (e.g., auth/, blog/).
  • Test: Use pytest to write tests for your app.

12. Conclusion

Flask’s flexibility and simplicity make it an excellent choice for building web applications with Python. From “Hello World” to full-featured apps with authentication and databases, Flask scales with your needs. By combining Flask with extensions like Flask-SQLAlchemy and Flask-Login, you can build robust, production-ready apps with minimal overhead.

Now it’s your turn—start building!

13. References