DEV Community

Cover image for 📝 Build a To-Do List App with Flask and Jinja2 — A Beginner’s Guide
likhitha manikonda
likhitha manikonda

Posted on

📝 Build a To-Do List App with Flask and Jinja2 — A Beginner’s Guide

If you're just starting out with Python web development, Flask is a great place to begin. In this tutorial, we’ll build a simple To-Do List app using Flask, SQLAlchemy, and Jinja2 templates. You’ll learn how to structure a Flask project, define models, create routes, and render dynamic HTML.


🧱 What You’ll Build

A web app where users can:

  • Add tasks
  • View incomplete and completed tasks
  • Mark tasks as complete

🗂️ Project Structure

/todo-app
│
├── app/
│   ├── __init__.py       # App setup and database config
│   ├── models.py         # Database model for tasks
│   ├── routes.py         # Web routes and logic
│   └── templates/
│       └── index.html    # HTML template using Jinja2
├── run.py                # Entry point to start the app
└── todo.db               # SQLite database (auto-created)
Enter fullscreen mode Exit fullscreen mode

🔧 Step-by-Step Explanation

1. __init__.py — Flask App Setup

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os

file_path = os.path.abspath(os.getcwd()) + "/todo.db"

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + file_path
db = SQLAlchemy(app)

from app import routes
Enter fullscreen mode Exit fullscreen mode

What’s happening:

  • Flask(__name__): Creates your Flask app.
  • SQLAlchemy(app): Connects your app to a SQLite database using SQLAlchemy.
  • todo.db: The database file that stores your tasks.
  • from app import routes: Loads your route definitions so Flask knows how to respond to web requests.

2. models.py — Define the To-Do Model

from app import db

class Todo(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    text = db.Column(db.String(200))
    complete = db.Column(db.Boolean)

    def __repr__(self):
        return self.text
Enter fullscreen mode Exit fullscreen mode

What’s happening:

  • Todo: This class defines the structure of your task table.
  • id: A unique identifier for each task.
  • text: The task description (e.g., “Buy groceries”).
  • complete: A boolean value to track whether the task is done.

3. routes.py — Define Web Routes

from flask import render_template, request, redirect, url_for
from app import app, db
from app.models import Todo
Enter fullscreen mode Exit fullscreen mode

Route: Homepage (/)

@app.route('/')
def index():
    incomplete = Todo.query.filter_by(complete=False).all()
    complete = Todo.query.filter_by(complete=True).all()
    return render_template('index.html', incomplete=incomplete, complete=complete)
Enter fullscreen mode Exit fullscreen mode
  • Queries the database for incomplete and complete tasks.
  • Passes them to the index.html template for display.

Route: Add a Task (/add)

@app.route('/add', methods=['POST'])
def add():
    todo = Todo(text=request.form['todoitem'], complete=False)
    db.session.add(todo)
    db.session.commit()
    return redirect(url_for('index'))
Enter fullscreen mode Exit fullscreen mode
  • Gets the task text from the form.
  • Creates a new Todo object and saves it to the database.
  • Redirects back to the homepage.

Route: Mark Task Complete (/complete/<id>)

@app.route('/complete/<id>')
def complete(id):
    todo = Todo.query.filter_by(id=int(id)).first()
    todo.complete = True
    db.session.commit()
    return redirect(url_for('index'))
Enter fullscreen mode Exit fullscreen mode
  • Finds the task by its ID.
  • Sets complete = True.
  • Saves the change and reloads the homepage.

4. run.py — Start the App

from app import app, db

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

What’s happening:

  • db.create_all(): Creates the Todo table in the database if it doesn’t exist.
  • app.run(debug=True): Starts the Flask development server and enables error messages.

5. index.html — Jinja2 Template

<!DOCTYPE html>
<html>
<head>
    <title>To-Do List</title>
</head>
<body>
    <h1>My To-Do List</h1>

    <form action="/add" method="post">
        <input type="text" name="todoitem" placeholder="Enter a task">
        <button type="submit">Add</button>
    </form>

    <h2>Incomplete Tasks</h2>
    <ul>
        {% for task in incomplete %}
            <li>{{ task.text }} <a href="/complete/{{ task.id }}">Complete</a></li>
        {% endfor %}
    </ul>

    <h2>Completed Tasks</h2>
    <ul>
        {% for task in complete %}
            <li>{{ task.text }}</li>
        {% endfor %}
    </ul>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

What’s happening:

  • Uses Jinja2 syntax ({{ }} and {% %}) to dynamically display tasks.
  • Loops through incomplete and complete lists passed from Flask.
  • Provides a form to add new tasks and links to mark tasks complete.

🧪 Common Errors & Fixes

❌ Error: no such table: todo

Fix: Make sure you call db.create_all() inside an app context:

with app.app_context():
    db.create_all()
Enter fullscreen mode Exit fullscreen mode

❌ Error: Working outside of application context

Fix: Wrap database operations inside app.app_context() to give Flask access to the app configuration.


Top comments (0)