Abdalla Harem | September 10, 2025 | 6 min read
Welcome to this hands-on mini-course on building a full-stack web application using Flask! If you’re new to web development or looking to level up your Python skills, this guide is for you. We’ll create a simple yet functional to-do list app that allows users to add, update, and delete tasks. We’ll integrate a SQLite database for data persistence and deploy it to Heroku for free hosting.
What You’ll Learn:
- Setting up a Flask app with routes and templates.
- Managing data with SQLite and Flask-SQLAlchemy.
- Creating a simple CRUD (Create, Read, Update, Delete) interface.
- Deploying to Heroku for free hosting.
Prerequisites: Basic Python knowledge. No prior Flask or web dev experience needed!
Tools You’ll Need:
- Python 3.12: Core language for Flask (download from python.org).
- VS Code: Code editor for writing Python and HTML (download from code.visualstudio.com).
- Terminal: Use Terminal (macOS/Linux) or PowerShell (Windows) for commands.
- Git: Version control for deployment (install from git-scm.com).
- Heroku CLI: For deploying to Heroku (install from devcenter.heroku.com).
- pip: Python package manager (comes with Python).
- Virtualenv: For isolated Python environments (included with Python).
- Web Browser: Chrome, Firefox, or Edge to test the app.
Let’s build this step-by-step!

Module 1: Setting Up Your Environment
Get your workspace ready with the right tools and structure.
- Create Project Directory:
- Tool: Terminal (macOS/Linux) or PowerShell (Windows).
- Run:
mkdir todo_app && cd todo_app
- Why? Organizes your project files.
2. Set Up Virtual Environment:
- Tool: Python (python3 or python command).
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
- Why? Isolates dependencies to avoid conflicts.
3. Install Dependencies:
- Tool: pip (Python package manager).
- Install Flask (web framework), Flask-SQLAlchemy (database ORM), and Gunicorn (production server):
pip install flask flask-sqlalchemy gunicorn
pip freeze > requirements.txt
- Why? requirements.txt ensures Heroku knows what to install.
4. Create Project Structure:
- Tool: Terminal or VS Code (File Explorer).
- Run:
mkdir templates static
touch app.py models.py requirements.txt Procfile runtime.txt
-
- Structure:
- app.py: Main app logic.
- models.py: Database models.
- templates/: HTML files.
- static/: CSS/JS files.
- Procfile & runtime.txt: Heroku configs.
- Why? Follows Flask’s conventions for scalability.
- Structure:
Artifact: Requirements File
Flask==3.0.3 Flask-SQLAlchemy==3.1.1 gunicorn==22.0.0 Werkzeug==3.0.3
Pro Tip: Use VS Code’s Python extension for linting and debugging.
Module 2: Defining the Database Model
We’ll use SQLite (a lightweight, file-based database) with Flask-SQLAlchemy to manage tasks.
- Tools:
- VS Code: To write models.py.
- Flask-SQLAlchemy: ORM for database interactions.
- SQLite: Built into Python (no separate install needed).
Create models.py:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Task(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
description = db.Column(db.String(500))
completed = db.Column(db.Boolean, default=False)
def __repr__(self):
return f'<Task {self.title}>'
Breakdown:
- db = SQLAlchemy(): Initializes ORM.
- Task class: Defines table structure.
- id: Unique identifier.
- title: Required task name (max 200 chars).
- description: Optional details.
- completed: Tracks completion status.
- __repr__: For debugging.
Key Learning: ORMs simplify database queries into Python objects, reducing manual SQL.
Module 3: Building the Flask App
This is the core—routes, logic, and database integration.
- Tools:
- VS Code: For coding app.py.
- Flask: Web framework for routing and rendering.
- SQLite: Stores data in tasks.db.
Create app.py:
from flask import Flask, render_template, request, redirect, url_for, flash
from models import db, Task
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-change-this-in-production'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
# Create database tables
with app.app_context():
db.create_all()
@app.route('/')
def index():
tasks = Task.query.all()
return render_template('index.html', tasks=tasks)
@app.route('/add', methods=['POST'])
def add_task():
title = request.form['title']
description = request.form['description']
if title:
new_task = Task(title=title, description=description)
db.session.add(new_task)
db.session.commit()
flash('Task added successfully!')
else:
flash('Title is required!')
return redirect(url_for('index'))
@app.route('/update/<int:task_id>', methods=['POST'])
def update_task(task_id):
task = Task.query.get_or_404(task_id)
task.completed = not task.completed
db.session.commit()
flash('Task updated!')
return redirect(url_for('index'))
@app.route('/delete/<int:task_id>', methods=['POST'])
def delete_task(task_id):
task = Task.query.get_or_404(task_id)
db.session.delete(task)
db.session.commit()
flash('Task deleted!')
return redirect(url_for('index'))
if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
app.run(host='0.0.0.0', port=port, debug=False)
Breakdown:
- Config: Secret key for security, SQLite URI, and disable tracking.
- Routes:
- /: Lists all tasks.
- /add: Creates new tasks.
- /update/<task_id>: Toggles completion.
- /delete/<task_id>: Deletes tasks.
- Flash: User feedback messages.
- Production-Ready: Uses os.environ.get(‘PORT’) for Heroku.
Pro Tip: Use url_for for dynamic URLs and flash for user feedback.
Module 4: Creating the Frontend
We’ll use HTML with Jinja2 templating and Bootstrap for styling.
- Tools:
- VS Code: For writing HTML.
- Bootstrap 5.1.3: Via CDN for styling (no install needed).
- Jinja2: Flask’s templating engine.
Create templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>To-Do List</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="container mt-5">
<h1 class="mb-4">My To-Do List</h1>
<!-- Add Task Form -->
<form method="POST" action="{{ url_for('add_task') }}" class="mb-4">
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control" name="title" placeholder="Task Title" required>
</div>
<div class="col-md-4">
<input type="text" class="form-control" name="description" placeholder="Description (optional)">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">Add Task</button>
</div>
</div>
</form>
<!-- Flash Messages -->
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-info">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Tasks List -->
<ul class="list-group">
{% for task in tasks %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<h5 class="{% if task.completed %}text-decoration-line-through text-muted{% endif %}">{{ task.title }}</h5>
{% if task.description %}
<p class="mb-0 text-muted">{{ task.description }}</p>
{% endif %}
</div>
<div>
<form method="POST" action="{{ url_for('update_task', task_id=task.id) }}" style="display: inline;">
<button type="submit" class="btn btn-sm btn-success">Toggle Complete</button>
</form>
<form method="POST" action="{{ url_for('delete_task', task_id=task.id) }}" style="display: inline;">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure?')">Delete</button>
</form>
</div>
</li>
{% endfor %}
</ul>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Breakdown:
- Bootstrap: Provides responsive design via CDN.
- Form: Submits to /add route.
- Jinja2: Loops through tasks, conditionally styles completed ones.
- Flash Messages: Displays success/error alerts.
Key Learning: Templates separate logic from UI. Bootstrap saves time on styling.
Module 5: Testing Locally
Test your app to ensure it works before deployment.
- Tools:
- Flask CLI: Run flask run.
- Web Browser: To view http://127.0.0.1:5000/.
- SQLite: Creates tasks.db automatically.
- Set environment variable (optional for dev):
export FLASK_APP=app.py # Windows: set FLASK_APP=app.py
2. Run: textflask run
Open the browser to http://127.0.0.1:5000/.
Test adding, updating, and deleting tasks.
Pro Tip: If errors occur, check the terminal output. Flask’s debug mode (debug=True locally) shows detailed logs.
Module 6: Preparing for Heroku
Heroku requires a few extra files for deployment.
- Tools:
- VS Code: For creating Procfile and runtime.txt.
- Git: For version control and pushing to Heroku.
- Create Procfile:
Procfile
web: gunicorn app:app
- Create runtime.txt:
python-3.12.3
- Verify requirements.txt includes gunicorn.
Note: SQLite works for testing but isn’t ideal for Heroku (data resets on dyno restarts). We’ll add PostgreSQL in the next module.
Module 7: Deploying to Heroku
Host your app online for free!
- Tools:
- Heroku CLI: For deployment commands.
- Git: To push code.
- PostgreSQL: Optional for persistent data (Heroku add-on).
- Install Heroku CLI: Follow Heroku’s guide.
- Deploy Steps:
- Login: heroku login.
- Create app: heroku create your-app-name (unique name).
- Initialize Git (if not done):
git init
git add .
git commit -m "Initial commit"
- Push to Heroku: git push heroku main.
- Scale dyno: heroku ps:scale web=1.
- Open app: heroku open.
3. Switch to PostgreSQL (recommended):
- Add free Postgres:
heroku addons:create heroku-postgresql:mini
- Update app.py in VS Code:
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL').replace("postgres://", "postgresql://")
- Commit and push again:
git add app.py
git commit -m “Switch to PostgreSQL”
git push heroku main
Troubleshooting:
- View logs: heroku logs –tail.
- If data resets: Ensure PostgreSQL is configured (SQLite is ephemeral on Heroku).
Key Learning: Heroku simplifies hosting but requires specific configs like Procfile and environment variables.
Wrapping Up: What You’ve Built
You’ve created a Flask to-do list app with:
- A SQLite database (local) or PostgreSQL (Heroku).
- CRUD functionality: Add, toggle, and delete tasks.
- A Bootstrap-styled frontend.
- A live deployment on Heroku.
Next Steps:
- Add user login with Flask-Login.
- Include task categories or due dates.
- Enhance styling with custom CSS in static/style.css.
- Explore Heroku add-ons like Redis for caching.
Best Practices:
- Change the secret key for production.
- Use .env files for sensitive configs (with python-dotenv).
- Commit code to GitHub for backup.
- Test edge cases (e.g., empty forms).
Tools Recap:
- Development: Python, VS Code, pip, virtualenv, Flask, Flask-SQLAlchemy, SQLite.
- Frontend: Bootstrap (CDN), Jinja2.
- Deployment: Git, Heroku CLI, Gunicorn, PostgreSQL.
If you hit issues, check Heroku logs or Flask docs. Share your app link in the comments! Happy coding! 🚀
Keywords: Flask, Python, web development, SQLite, Flask-SQLAlchemy, Heroku, PostgreSQL, Jinja2, Bootstrap, to-do list app, CRUD, web app tutorial, backend development, full-stack Python, deployment, Gunicorn, cloud hosting, responsive design, beginner coding, Python project
Hashtags: #Flask #Python #WebDev #LearnToCode #PythonProgramming #FlaskTutorial #WebApp #Heroku #SQLAlchemy #SQLite #PostgreSQL #Jinja2 #Bootstrap #ToDoApp #CRUD #Coding #BackendDev #FullStack #PythonProjects #TechTutorial



