Skip to content

Python Flask Beginner Tutorial - Todo App

Learn how to write a TODO App with Flask in this Crash Course.


Learn how to write a TODO App with Flask in this Crash Course. Flask is one of the most popular web frameworks written in Python. Flask is a lightweight framework that is perfect for beginners. It is designed to make getting started quick and easy, with the ability to scale up to complex applications. Learn how to:

  • Install and Setup Flask
  • Define routes
  • Use templates
  • Use a Database (we use SQLAlchemy and SQLite Database)
  • Build TODO App functionality
  • Add styling with Semantic UI

You can find the complete code on GitHub.

Setup

Create project with virtual environment

$ mkdir myproject
$ cd myproject
$ python3 -m venv venv

Activate it

$ . venv/bin/activate

or on Windows

venv\Scripts\activate

Install Flask and Flask-SQLAlchemy which is used for our database.

$ pip install Flask
$ pip install Flask-SQLAlchemy

Create the Hello World app

Create the app.py file and insert:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == "__main__":
    app.run(debug=True)
For each url/route we want, we have to create a function and decorate it with @app.route('path/to/your/url'). In this case we only use a forward slash /because this is going to be our start page. Note that we set debug=True so we don't have to reload our server each time we make a change in our code.

Run the app

Run

$ python app.py
Now we can go to http://127.0.0.1:5000/ and inspect our first running app!

Add the database

We have to import SQLAlchemy, set some config parameters for our database, create the database, and then create a model class for our todo items. Our model needs three entries: An id, a title, and a flag if the task is completed or not. We also call db.create_all()before running our app.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

# /// = relative path, //// = absolute path
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)


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


@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == "__main__":
    db.create_all()
    app.run(debug=True)

Change the home page to list all the todos

For this we import render_template and render a proper html instead of just a string. We will create the template file in the next step. Replace the hello_world function with this:

from flask import Flask, render_template

...

@app.route("/")
def home():
    todo_list = Todo.query.all()
    return render_template("base.html", todo_list=todo_list)

Create the template file

Create a folder named templates in the root folder of your project. It has to be this name because Flask is looking for this directory! Now create the base.html file in this templates folder and insert:

!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todo App</title>
</head>

<body>

    <h1>To Do App</h1>

    {% for todo in todo_list %}

    <p>{{todo.id }} | {{ todo.title }}</p>

    {% if todo.complete == False %}
    <span>Not Complete</span>
    {% else %}
    <span>Completed</span>
    {% endif %}

    {% endfor %}

</body>

</html>
This syntax uses the Jinja template engine which allows us to access our Python list todo_listthat we passed as argument to the template. It also allows us to use control flows like for-loops and if-statements. The syntax looks similar to Python, but it is not the same. Check out the documentation if you want to learn more about this.

Add routes to add, delete, and update new todos

Add this to your app.py:

from flask import Flask, render_template, request, redirect, url_for

@app.route("/add", methods=["POST"])
def add():
    title = request.form.get("title")
    new_todo = Todo(title=title, complete=False)
    db.session.add(new_todo)
    db.session.commit()
    return redirect(url_for("home"))

@app.route("/update/<int:todo_id>")
def update(todo_id):
    todo = Todo.query.filter_by(id=todo_id).first()
    todo.complete = not todo.complete
    db.session.commit()
    return redirect(url_for("home"))

@app.route("/delete/<int:todo_id>")
def delete(todo_id):
    todo = Todo.query.filter_by(id=todo_id).first()
    db.session.delete(todo)
    db.session.commit()
    return redirect(url_for("home"))
For each of our operations we add a new route that performs the desired operation with our todo items, updates the database, and then redirects us to the home page.

We also have to add this to our base.html:

!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todo App</title>
</head>

<body>

    <h1>To Do App</h1>

    <form action="/add" method="post">
        <div>
            <label>Todo Title</label>
            <input type="text" name="title" placeholder="Enter Todo...">
            <br>
            <button type="submit">Add</button>
        </div>
    </form>

    <hr>

    {% for todo in todo_list %}

    <p>{{todo.id }} | {{ todo.title }}</p>

    {% if todo.complete == False %}
    <span>Not Complete</span>
    {% else %}
    <span>Completed</span>
    {% endif %}

    <a href="/update/{{ todo.id }}">Update</a>
    <a href="/delete/{{ todo.id }}">Delete</a>

    {% endfor %}
</body>

</html>
We added a form to add new todos which will direct us to the /add route. We also added two a tags that will direct us the the routes for updating or deleting the item with the given id.

Congratulations! Your app is now fully functional! Now let's improve the UI a little bit...

Add styling with Semantic UI

For styling we use Semantic UI. This is a development framework that helps create beautiful, responsive layouts using human-friendly HTML. We only have to add class names to our html and don't have to worry about the css code. For example <a class="ui blue button" href="...">Update</a>will turn our simple a tag into a nice looking blue button. For integrating the Semantic UI in our html, we use a CDN. See the official docs for more info.

Put this into the head section:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>

Now we just have to add some class names to add the styling. The final html looks like this:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todo App</title>

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
    <script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
</head>

<body>
    <div style="margin-top: 50px;" class="ui container">
        <h1 class="ui center aligned header">To Do App</h1>

        <form class="ui form" action="/add" method="post">
            <div class="field">
                <label>Todo Title</label>
                <input type="text" name="title" placeholder="Enter Todo..."><br>
            </div>
            <button class="ui blue button" type="submit">Add</button>
        </form>

        <hr>

        {% for todo in todo_list %}
        <div class="ui segment">
            <p class="ui big header">{{todo.id }} | {{ todo.title }}</p>

            {% if todo.complete == False %}
            <span class="ui gray label">Not Complete</span>
            {% else %}
            <span class="ui green label">Completed</span>
            {% endif %}

            <a class="ui blue button" href="/update/{{ todo.id }}">Update</a>
            <a class="ui red button" href="/delete/{{ todo.id }}">Delete</a>
        </div>
        {% endfor %}
    </div>
</body>

</html>

Now if we reload the site, our app should look much nicer! Congratulations again! You completed the whole tutorial! I hope you enjoyed it and now have some basic knowledge of web app development with the Flask framework! As always, if you have any questions or feedback don't hesitate to reach out on Twitter or YouTube.


FREE VS Code / PyCharm Extensions I Use

✅ Write cleaner code with Sourcery, instant refactoring suggestions: Link*


Python Problem-Solving Bootcamp

🚀 Solve 42 programming puzzles over the course of 21 days: Link*

* These are affiliate link. By clicking on it you will not have any additional costs. Instead, you will support my project. Thank you! 🙏