Skip to main content

Flask: Mini Projects with Source Code

Flask: Mini Projects

Flask’s lightweight and flexible nature makes it ideal for building small-scale web applications to practice core concepts like routing, templating, database integration, and authentication. Below are five mini-project ideas using Flask, each designed to reinforce key skills while creating functional applications. Each project includes a description, key features, code examples, and explanations to help you learn Flask effectively. The projects leverage Flask’s core components, powered by Werkzeug for routing and Jinja2 for templating, along with extensions like Flask-SQLAlchemy and Flask-WTF.


01. Project 1: To-Do List App

Description: A simple to-do list app where users can add, mark as complete, and delete tasks. This project introduces Flask routes, templates, and basic database operations.

Key Features:

  • Add new tasks
  • Mark tasks as complete
  • Delete tasks
  • Display tasks in a list

Example: To-Do List App


# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret-key'
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.db'
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

    db.init_app(app)

    from app.routes import todo_bp
    app.register_blueprint(todo_bp)

    with app.app_context():
        db.create_all()

    return app

# app/models.py
from app import db

class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    completed = db.Column(db.Boolean, default=False)

# app/routes.py
from flask import Blueprint, render_template, request, redirect, url_for
from app import db
from app.models import Task

todo_bp = Blueprint('todo', __name__, template_folder='templates')

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

@todo_bp.route('/add', methods=['POST'])
def add_task():
    title = request.form.get('title')
    if title:
        task = Task(title=title)
        db.session.add(task)
        db.session.commit()
    return redirect(url_for('todo.index'))

@todo_bp.route('/complete/<int:task_id>')
def complete_task(task_id):
    task = Task.query.get_or_404(task_id)
    task.completed = True
    db.session.commit()
    return redirect(url_for('todo.index'))

@todo_bp.route('/delete/<int:task_id>')
def delete_task(task_id):
    task = Task.query.get_or_404(task_id)
    db.session.delete(task)
    db.session.commit()
    return redirect(url_for('todo.index'))

# app/templates/index.html
<!DOCTYPE html>
<html>
<head><title>To-Do List</title></head>
<body>
    <h2>To-Do List</h2>
    <form method="post" action="{{ url_for('todo.add_task') }}">
        <input type="text" name="title" required>
        <button type="submit">Add Task</button>
    </form>
    <ul>
        {% for task in tasks %}
            <li>
                {{ task.title }} {% if task.completed %}(Completed){% endif %}
                {% if not task.completed %}
                    <a href="{{ url_for('todo.complete_task', task_id=task.id) }}">Complete</a>
                {% endif %}
                <a href="{{ url_for('todo.delete_task', task_id=task.id) }}">Delete</a>
            </li>
        {% endfor %}
    </ul>
</body>
</html>

# run.py
from app import create_app

app = create_app()

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

Setup Command:


pip install flask flask-sqlalchemy
export FLASK_APP=run
flask run
        

Output: (In browser at http://127.0.0.1:5000)


<h2>To-Do List</h2>
<form method="post" action="/add">
<ul>
    <li>Buy groceries <a href="/complete/1">Complete</a> <a href="/delete/1">Delete</a></li>
</ul>
        

Explanation:

  • Routes: Handle task creation, completion, and deletion using Werkzeug routing.
  • Database: SQLAlchemy stores tasks with title and completion status.
  • Templates: Jinja2 renders the task list dynamically.
  • Learning Outcome: Basic CRUD operations and database integration.

02. Project 2: Weather Dashboard

Description: A dashboard that fetches and displays weather data for a user-specified city using an external API (e.g., OpenWeatherMap). This project teaches API integration and form handling.

Key Features:

  • Input city name
  • Display current weather (temperature, description)
  • Handle API errors

Example: Weather Dashboard


# app/__init__.py
from flask import Flask

def create_app():
    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret-key'

    from app.routes import weather_bp
    app.register_blueprint(weather_bp)

    return app

# app/routes.py
from flask import Blueprint, render_template, request
import requests

weather_bp = Blueprint('weather', __name__, template_folder='templates')

@weather_bp.route('/', methods=['GET', 'POST'])
def index():
    weather = None
    error = None
    if request.method == 'POST':
        city = request.form.get('city')
        api_key = 'your-openweathermap-api-key'
        url = f'http://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric'
        response = requests.get(url)
        if response.status_code == 200:
            data = response.json()
            weather = {
                'city': city,
                'temp': data['main']['temp'],
                'description': data['weather'][0]['description']
            }
        else:
            error = 'City not found'
    return render_template('index.html', weather=weather, error=error)

# app/templates/index.html
<!DOCTYPE html>
<html>
<head><title>Weather Dashboard</title></head>
<body>
    <h2>Weather Dashboard</h2>
    <form method="post">
        <input type="text" name="city" placeholder="Enter city" required>
        <button type="submit">Get Weather</button>
    </form>
    {% if error %}
        <p style="color: red;">{{ error }}</p>
    {% endif %}
    {% if weather %}
        <h3>{{ weather.city }}</h3>
        <p>Temperature: {{ weather.temp }}°C</p>
        <p>Description: {{ weather.description }}</p>
    {% endif %}
</body>
</html>

# run.py
from app import create_app

app = create_app()

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

Setup Command:


pip install flask requests
export FLASK_APP=run
flask run
        

Output: (In browser after entering "London")


<h2>Weather Dashboard</h2>
<form method="post">
<h3>London</h3>
<p>Temperature: 15°C</p>
<p>Description: clear sky</p>
        

Explanation:

  • API Integration: Uses requests to fetch weather data.
  • Forms: Handles user input for city names.
  • Templates: Jinja2 conditionally renders weather or error messages.
  • Learning Outcome: Working with external APIs and error handling.

Note: Replace your-openweathermap-api-key with a valid API key from OpenWeatherMap.


03. Project 3: URL Shortener

Description: A URL shortener that generates short links for long URLs and redirects users to the original URL. This project focuses on database usage and URL handling.

Key Features:

  • Create short URLs
  • Redirect to original URLs
  • Store URLs in a database

Example: URL Shortener


# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret-key'
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///urls.db'
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

    db.init_app(app)

    from app.routes import url_bp
    app.register_blueprint(url_bp)

    with app.app_context():
        db.create_all()

    return app

# app/models.py
from app import db
import string, random

def generate_short_id():
    return ''.join(random.choices(string.ascii_letters + string.digits, k=6))

class URL(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    original_url = db.Column(db.String(500), nullable=False)
    short_id = db.Column(db.String(6), unique=True, nullable=False)

# app/routes.py
from flask import Blueprint, render_template, request, redirect, url_for
from app import db
from app.models import URL, generate_short_id

url_bp = Blueprint('url', __name__, template_folder='templates')

@url_bp.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        original_url = request.form.get('url')
        short_id = generate_short_id()
        while URL.query.filter_by(short_id=short_id).first():
            short_id = generate_short_id()
        url = URL(original_url=original_url, short_id=short_id)
        db.session.add(url)
        db.session.commit()
        short_url = request.host_url + short_id
        return render_template('index.html', short_url=short_url)
    return render_template('index.html')

@url_bp.route('/<short_id>')
def redirect_url(short_id):
    url = URL.query.filter_by(short_id=short_id).first_or_404()
    return redirect(url.original_url)

# app/templates/index.html
<!DOCTYPE html>
<html>
<head><title>URL Shortener</title></head>
<body>
    <h2>URL Shortener</h2>
    <form method="post">
        <input type="url" name="url" placeholder="Enter URL" required>
        <button type="submit">Shorten</button>
    </form>
    {% if short_url %}
        <p>Short URL: <a href="{{ short_url }}">{{ short_url }}</a></p>
    {% endif %}
</body>
</html>

# run.py
from app import create_app

app = create_app()

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

Setup Command:


pip install flask flask-sqlalchemy
export FLASK_APP=run
flask run
        

Output: (In browser after shortening a URL)


<h2>URL Shortener</h2>
<form method="post">
<p>Short URL: <a href="http://127.0.0.1:5000/abc123">http://127.0.0.1:5000/abc123</a></p>
        

Explanation:

  • Database: Stores original and short URLs using SQLAlchemy.
  • Routing: Redirects short IDs to original URLs with Werkzeug.
  • Templates: Jinja2 displays the shortened URL.
  • Learning Outcome: URL manipulation and database-driven redirects.

04. Project 4: Guestbook

Description: A guestbook app where users can leave messages that are displayed publicly. This project introduces form validation and basic security practices.

Key Features:

  • Submit messages with name and text
  • Display all messages
  • Validate user input

Example: Guestbook App


# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret-key'
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///guestbook.db'
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

    db.init_app(app)

    from app.routes import guestbook_bp
    app.register_blueprint(guestbook_bp)

    with app.app_context():
        db.create_all()

    return app

# app/models.py
from app import db
from datetime import datetime

class Message(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), nullable=False)
    text = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

# app/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Length

class MessageForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired(), Length(max=50)])
    text = TextAreaField('Message', validators=[DataRequired(), Length(max=500)])
    submit = SubmitField('Submit')

# app/routes.py
from flask import Blueprint, render_template, redirect, url_for
from app import db
from app.models import Message
from app.forms import MessageForm

guestbook_bp = Blueprint('guestbook', __name__, template_folder='templates')

@guestbook_bp.route('/', methods=['GET', 'POST'])
def index():
    form = MessageForm()
    if form.validate_on_submit():
        message = Message(name=form.name.data, text=form.text.data)
        db.session.add(message)
        db.session.commit()
        return redirect(url_for('guestbook.index'))
    messages = Message.query.order_by(Message.created_at.desc()).all()
    return render_template('index.html', form=form, messages=messages)

# app/templates/index.html
<!DOCTYPE html>
<html>
<head><title>Guestbook</title></head>
<body>
    <h2>Guestbook</h2>
    <form method="post">
        {{ form.hidden_tag() }}
        {{ form.name.label }} {{ form.name() }}<br>
        {{ form.text.label }} {{ form.text() }}<br>
        {{ form.submit() }}
    </form>
    <h3>Messages</h3>
    {% for message in messages %}
        <p><strong>{{ message.name }}</strong> ({{ message.created_at.strftime('%Y-%m-%d') }}): {{ message.text }}</p>
    {% endfor %}
</body>
</html>

# run.py
from app import create_app

app = create_app()

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

Setup Command:


pip install flask flask-sqlalchemy flask-wtf
export FLASK_APP=run
flask run
        

Output: (In browser)


<h2>Guestbook</h2>
<form method="post">
<h3>Messages</h3>
<p><strong>Alice</strong> (2025-05-12): Great site!</p>
        

Explanation:

  • Forms: Flask-WTF validates user input for security.
  • Database: Stores messages with timestamps using SQLAlchemy.
  • Templates: Jinja2 displays messages dynamically.
  • Learning Outcome: Form validation and secure input handling.

05. Project 5: Expense Tracker

Description: An app to track personal expenses, allowing users to add, categorize, and view expenses. This project introduces authentication and data aggregation.

Key Features:

  • User login
  • Add expenses with amount and category
  • View expense summary

Example: Expense Tracker


# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager

db = SQLAlchemy()
login_manager = LoginManager()

def create_app():
    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret-key'
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///expenses.db'
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

    db.init_app(app)
    login_manager.init_app(app)
    login_manager.login_view = 'auth.login'

    from app.routes import auth_bp, expense_bp
    app.register_blueprint(auth_bp)
    app.register_blueprint(expense_bp)

    with app.app_context():
        db.create_all()

    return app

# app/models.py
from app import db
from flask_login import UserMixin
from datetime import datetime

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True, nullable=False)
    password = db.Column(db.String(100), nullable=False)

class Expense(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    amount = db.Column(db.Float, nullable=False)
    category = db.Column(db.String(50), nullable=False)
    date = db.Column(db.DateTime, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship('User', backref='expenses')

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

class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), Length(min=3, max=50)])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Login')

class ExpenseForm(FlaskForm):
    amount = FloatField('Amount', validators=[DataRequired()])
    category = StringField('Category', validators=[DataRequired(), Length(max=50)])
    submit = SubmitField('Add Expense')

# app/routes.py
from flask import Blueprint, render_template, redirect, url_for, request, flash
from flask_login import login_user, logout_user, login_required, current_user
from app import db
from app.models import User, Expense
from app.forms import LoginForm, ExpenseForm
import hashlib

auth_bp = Blueprint('auth', __name__, template_folder='templates')
expense_bp = Blueprint('expense', __name__, template_folder='templates')

@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user and user.password == hashlib.sha256(form.password.data.encode()).hexdigest():
            login_user(user)
            return redirect(url_for('expense.index'))
        flash('Invalid credentials')
    return render_template('login.html', form=form)

@auth_bp.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('auth.login'))

@expense_bp.route('/', methods=['GET', 'POST'])
@login_required
def index():
    form = ExpenseForm()
    if form.validate_on_submit():
        expense = Expense(
            amount=form.amount.data,
            category=form.category.data,
            user_id=current_user.id
        )
        db.session.add(expense)
        db.session.commit()
        return redirect(url_for('expense.index'))
    expenses = Expense.query.filter_by(user_id=current_user.id).all()
    total = sum(expense.amount for expense in expenses)
    return render_template('index.html', form=form, expenses=expenses, total=total)

# app/templates/login.html
<!DOCTYPE html>
<html>
<head><title>Login</title></head>
<body>
    <h2>Login</h2>
    {% for message in get_flashed_messages() %}
        <p style="color: red;">{{ message }}</p>
    {% endfor %}
    <form method="post">
        {{ form.hidden_tag() }}
        {{ form.username.label }} {{ form.username() }}<br>
        {{ form.password.label }} {{ form.password() }}<br>
        {{ form.submit() }}
    </form>
</body>
</html>

# app/templates/index.html
<!DOCTYPE html>
<html>
<head><title>Expense Tracker</title></head>
<body>
    <h2>Expense Tracker</h2>
    <a href="{{ url_for('auth.logout') }}">Logout</a>
    <form method="post">
        {{ form.hidden_tag() }}
        {{ form.amount.label }} {{ form.amount() }}<br>
        {{ form.category.label }} {{ form.category() }}<br>
        {{ form.submit() }}
    </form>
    <h3>Expenses (Total: ${{ total }})</h3>
    <ul>
        {% for expense in expenses %}
            <li>{{ expense.category }}: ${{ expense.amount }} ({{ expense.date.strftime('%Y-%m-%d') }})</li>
        {% endfor %}
    </ul>
</body>
</html>

# run.py
from app import create_app

app = create_app()

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

Setup Command:


pip install flask flask-sqlalchemy flask-login flask-wtf
export FLASK_APP=run
flask run
        

Output: (In browser after adding an expense)


<h2>Expense Tracker</h2>
<form method="post">
<h3>Expenses (Total: $50)</h3>
<ul>
    <li>Food: $50 (2025-05-12)</li>
</ul>
        

Explanation:

  • Authentication: Flask-Login secures user-specific expenses.
  • Database: SQLAlchemy stores expenses with user association.
  • Templates: Jinja2 displays expenses and totals.
  • Learning Outcome: User authentication and data aggregation.

Note: For production, use a secure password hashing library like bcrypt instead of hashlib.


Best Practices for Flask Mini Projects

  • Modular Design: Use blueprints and application factories for scalability.
  • Validation: Implement form validation with Flask-WTF to ensure secure input.
  • Testing: Add unit tests with pytest to verify functionality.
  • Documentation: Include docstrings and comments for clarity.
  • Avoid Monolithic Code: Separate routes, models, and templates to prevent clutter.

Example: Incorrect Monolithic App


# app.py
from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        # All logic in one place
        return render_template('index.html')
    return render_template('index.html')

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

Explanation:

  • Single file with mixed logic is hard to maintain.
  • Solution: Use blueprints and separate concerns.

Conclusion

These Flask mini projects—To-Do List, Weather Dashboard, URL Shortener, Guestbook, and Expense Tracker—provide hands-on experience with Flask’s core features, including Werkzeug routing, Jinja2 templating, and extensions like Flask-SQLAlchemy, Flask-Login, and Flask-WTF. Each project focuses on specific skills, from database integration to API handling and authentication, making them ideal for learning and portfolio building. By following best practices like modular design and validation, you can create robust, maintainable Flask applications.

  • Start small: Begin with the To-Do List or Guestbook for basics.
  • Explore APIs: Try the Weather Dashboard for external data.
  • Add complexity: Build the Expense Tracker for authentication and data aggregation.
  • Enhance projects: Add features like user registration or pagination to deepen your skills.

With these mini projects, you’ll gain confidence in Flask development and be well-prepared for larger applications!

Comments