Career Paths

Web Development with Flask

Learn how to build dynamic web applications with Python using the popular micro-framework Flask. Start from the basics and build a complete project.

Our Project: Todo List App

We will create a simple but fully functional Todo List application, where users can view, add, and delete tasks. We will learn about routing, templates, and form handling.

Core Technologies We'll Use:

Python
Flask
HTML
CSS
Step 1 / 7

Step 1: Environment Setup and Project Structure

We understand the importance of virtual environments and create the basic folder structure for our project. This step is performed in your computer's terminal.

Welcome to the Web Development path! Every professional project starts with a well-organized structure. Before writing a single line of code, we'll set up our environment.

1. Virtual Environment

A virtual environment is an isolated space for each project, allowing us to install libraries without affecting other projects. It is an absolutely essential practice.

Open your terminal (Command Prompt, PowerShell, Terminal) and run the following commands one by one:

# Create the project folder and navigate into it
mkdir my_todo_app
cd my_todo_app

# Create the virtual environment named 'venv'
python -m venv venv

# Activate it:
# For Windows:
.\venv\Scripts\activate
# For macOS/Linux:
source venv/bin/activate

2. Install Flask

With the virtual environment activated (you should see (venv) at the beginning of your terminal prompt), install Flask:

pip install Flask

3. Project Folder Structure

Flask applications follow a specific structure. From your terminal, inside the my_todo_app folder, create the following folders and empty files:

# Create the 'templates' and 'static' folders
mkdir templates
mkdir static

# Create the empty files (commands may vary by OS)
# For macOS/Linux:
touch app.py templates/index.html static/style.css
# For Windows:
echo. > app.py
echo. > templates\index.html
echo. > static\style.css

After these commands, your my_todo_app folder structure should be as follows:

/my_todo_app
|-- venv/               (already exists)
|-- app.py              (Our main application file)
|-- templates/          (Folder for HTML files)
|   |-- index.html      (The HTML for our main page)
|-- static/             (Folder for CSS, JavaScript, images)
    |-- style.css       (Our CSS file for styling)

This organization is crucial because Flask automatically looks for HTML files in the templates folder and for static files (CSS, JS) in the static folder.

Step 2 / 7

Step 2: The Basic Flask Application

We write the minimal code in the app.py file to create a functional web application that simply displays a message.

Now we will write our first code. Open the app.py file with your favorite editor (e.g., VS Code) and add the following content.

  • from flask import Flask: We import the Flask class, which is the core of the application.
  • app = Flask(__name__): We create an instance of our application.
  • @app.route("/"): This is a "decorator" that tells Flask: "When someone visits the root URL ('/'), execute the following function".
  • def home():: The function that is executed for the above route. It simply returns a string.
  • if __name__ == "__main__":: This ensures that the development server will only start when we run this file directly.

# app.py
from flask import Flask

# Create the application object
app = Flask(__name__)

# Define a "route" for the home page
@app.route("/")
def home():
    return "Hello, World from Flask!"

# Start the development server
if __name__ == "__main__":
    app.run(debug=True)
Step 3 / 7

Step 3: Using HTML Templates

We replace the simple message with a full HTML file, learning how Flask renders dynamic pages.

Instead of returning plain text, we will use Flask's render_template function to render the index.html file we created earlier.

1. Modify app.py

We import render_template from flask and change the home function to call it, instead of returning a string.

2. Create templates/index.html

Now, we open the templates/index.html file and add the basic HTML structure. This is the file the user will see.


# --- app.py (Updated) ---
from flask import Flask, render_template # Add render_template

app = Flask(__name__)

@app.route("/")
def home():
    # Instead of text, we return the rendered template
    return render_template("index.html")

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

# --- templates/index.html (New content) ---
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Todo List App with Flask</title>
</head>
<body>
    <h1>My Todo List</h1>
    <p>Soon our tasks will appear here!</p>
</body>
</html>
Step 4 / 7

Step 4: Dynamic Data and Loops in Templates

We pass data (a list of tasks) from Python to HTML and use the Jinja2 engine to display it dynamically.

Now we'll make our application dynamic. We'll create a list of tasks in app.py and pass it to the template for display.

1. Update app.py

We create a list of dictionaries. Each dictionary represents a task. Then, we pass this list as an argument to render_template: render_template("index.html", todos=todos_list). The name on the left (todos) is the name of the variable that will be available inside the HTML.

2. Update templates/index.html

We use the Jinja2 engine's syntax. With {% for ... %} we start a loop. With {{ ... }} we print the value of a variable. The {% else %} is executed if the list is empty. {% endfor %} closes the loop. With {{ 'done' if todo.done else '' }}, we dynamically add a CSS class.


# --- app.py (Updated) ---
from flask import Flask, render_template

app = Flask(__name__)

# Our "database" for now.
todos_list = [
    {"id": 1, "task": "Learn Python", "done": True},
    {"id": 2, "task": "Build a web app with Flask", "done": False},
    {"id": 3, "task": "Go shopping", "done": False}
]

@app.route("/")
def home():
    # We pass the 'todos_list' to the template with the name 'todos'
    return render_template("index.html", todos=todos_list)

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

# --- templates/index.html (Updated) ---
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Todo List App with Flask</title>
</head>
<body>
    <h1>My Todo List</h1>
    <ul>
        {% for todo in todos %}
            <li class="{{ 'done' if todo.done else '' }}">
                {{ todo.task }}
            </li>
        {% else %}
            <li>No tasks yet!</li>
        {% endfor %}
    </ul>
</body>
</html>
Step 5 / 7

Step 5: Styling with CSS

We add basic styling to our application by linking the CSS file to the HTML template for a more professional look.

A functional application is good, but a beautiful application is better! We will add CSS rules to the static/style.css file and link it to index.html.

1. Update static/style.css

Open the file and add the following CSS rules to give the application a basic, clean look.

2. Link CSS in templates/index.html

The connection is made with the <link> tag in HTML, again using Flask's url_for function to find the correct path: url_for('static', filename='style.css'). This tells Flask to look in the static folder for the style.css file.


# --- static/style.css (New content) ---
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background-color: #f0f2f5; color: #333; line-height: 1.6; display: grid; place-items: center; min-height: 100vh; margin: 0; }
.container { width: 100%; max-width: 600px; padding: 2rem; background-color: #fff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
h1 { color: #1a237e; text-align: center; margin-bottom: 1.5rem; }
ul { list-style: none; padding: 0; }
li { padding: 0.75rem 0.5rem; border-bottom: 1px solid #eee; }
li.done { text-decoration: line-through; color: #aaa; }

# --- templates/index.html (Updated) ---
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Todo List App with Flask</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <div class="container">
        <h1>My Todo List</h1>
        <ul>
            {% for todo in todos %}
                <li class="{{ 'done' if todo.done else '' }}">
                    {{ todo.task }}
                </li>
            {% else %}
                <li>No tasks yet!</li>
            {% endfor %}
        </ul>
    </div>
</body>
</html>
Step 6 / 7

Step 6: Adding New Tasks with a Form

We implement an HTML form and a new route in Flask to allow the user to add new tasks to the list.

To make the application interactive, we need a <form>. The form will send its data to the /add address using the POST method.

1. Update app.py

We import request, redirect, and url_for. We create a new function add_todo and bind it to the /add route, specifying that it only accepts POST requests (methods=["POST"]). Inside the function, we use request.form.get("task_text") to get the text the user wrote. Finally, we use the redirect(url_for("home")) function to redirect the user back to the home page.

2. Update templates/index.html

We add a form after the task list. The action="{{ url_for('add_todo') }}" ensures that the form will be sent to the correct Flask route.


# --- app.py (Updated) ---
from flask import Flask, render_template, request, redirect, url_for # Add request, redirect, url_for

app = Flask(__name__)

todos_list = [
    {"id": 1, "task": "Learn Python", "done": True},
    {"id": 2, "task": "Build a web app with Flask", "done": False},
]
next_id = 3 # Simple counter for unique ids

@app.route("/")
def home():
    return render_template("index.html", todos=todos_list)

@app.route("/add", methods=["POST"])
def add_todo():
    global next_id
    task_text = request.form.get("task_text")
    if task_text:
        todos_list.append({"id": next_id, "task": task_text, "done": False})
        next_id += 1
    return redirect(url_for("home"))

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

# --- templates/index.html (Updated) ---
# ... (inside the .container div, after the </ul>) ...
    <hr>
    <form action="{{ url_for('add_todo') }}" method="POST" class="add-form">
        <input type="text" name="task_text" placeholder="New task description..." required>
        <button type="submit">Add</button>
    </form>
# ...
Step 7 / 7

Step 7: Updating and Deleting Tasks

We add the ability to delete and mark a task as complete, using dynamic URL routes.

We will add the final functionalities: toggling the status and deleting a task. We will do this by creating dynamic URL routes that contain the task ID, e.g., /toggle/<int:todo_id>. This tells Flask to expect a URL like '/toggle/1' and to pass the number 1 as an argument to our function.

For toggling, the toggle_todo function finds the task and reverses the value of done. For deletion, the delete_todo function creates a new list containing all tasks except the one we want to delete.


# --- app.py (Updated) ---
# ... (up to the add_todo function) ...

@app.route("/toggle/<int:todo_id>")
def toggle_todo(todo_id):
    for todo in todos_list:
        if todo["id"] == todo_id:
            todo["done"] = not todo["done"]
            break
    return redirect(url_for("home"))

@app.route("/delete/<int:todo_id>")
def delete_todo(todo_id):
    global todos_list
    todos_list = [todo for todo in todos_list if todo["id"] != todo_id]
    return redirect(url_for("home"))

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

# --- templates/index.html (Updated) ---
# ... (replace the old <li> with this) ...
{% for todo in todos %}
    <li class="todo-item {{ 'done' if todo.done else '' }}">
        <a href="{{ url_for('toggle_todo', todo_id=todo.id) }}" class="toggle-button">
            {% if todo.done %}✓{% else %}○{% endif %}
        </a>
        <span class="task-text">{{ todo.task }}</span>
        <a href="{{ url_for('delete_todo', todo_id=todo.id) }}" class="delete-button">✕</a>
    </li>
{% else %}
# ...

Project Completion & Next Steps

Congratulations! You have completed the path and now have the full code for the project.

This is the final, complete code for the application. You can copy it, run it locally on your computer (after installing the necessary libraries with `pip`), and experiment by adding your own features!


# --- app.py ---
from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

# In-memory "database"
todos = [
    {"id": 1, "task": "Learn Python", "done": True},
    {"id": 2, "task": "Build a web app with Flask", "done": False},
]
next_id = 3

@app.route("/")
def index():
    return render_template("index.html", todos=todos)

@app.route("/add", methods=["POST"])
def add_todo():
    global next_id
    task_text = request.form.get("task_text")
    if task_text:
        todos.append({"id": next_id, "task": task_text, "done": False})
        next_id += 1
    return redirect(url_for("index"))

@app.route("/toggle/<int:todo_id>")
def toggle_todo(todo_id):
    for todo in todos:
        if todo["id"] == todo_id:
            todo["done"] = not todo["done"]
            break
    return redirect(url_for("index"))

@app.route("/delete/<int:todo_id>")
def delete_todo(todo_id):
    global todos
    todos = [todo for todo in todos if todo["id"] != todo_id]
    return redirect(url_for("index"))

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

# --- templates/index.html ---
# <!DOCTYPE html>
# <html lang="en">
# <head>
#     <meta charset="UTF-8">
#     <title>Todo List App</title>
#     <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
# </head>
# <body>
#     <div class="container">
#         <h1>My Todo List</h1>
#         <ul class="todo-list">
#             {% for todo in todos %}
#                 <li class="todo-item {{ 'done' if todo.done else '' }}">
#                     <a href="{{ url_for('toggle_todo', todo_id=todo.id) }}" class="toggle-button">
#                         {% if todo.done %}✓{% else %}○{% endif %}
#                     </a>
#                     <span class="task-text">{{ todo.task }}</span>
#                     <a href="{{ url_for('delete_todo', todo_id=todo.id) }}" class="delete-button">✕</a>
#                 </li>
#             {% else %}
#                 <li class="empty-message">No tasks yet!</li>
#             {% endfor %}
#         </ul>
#         <hr>
#         <form action="{{ url_for('add_todo') }}" method="POST" class="add-form">
#             <input type="text" name="task_text" placeholder="New task..." required>
#             <button type="submit">Add</button>
#         </form>
#     </div>
# </body>
# </html>

# --- static/style.css ---
# body { font-family: sans-serif; background-color: #f0f2f5; color: #333; }
# .container { max-width: 600px; margin: 2rem auto; padding: 2rem; background: #fff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
# h1 { text-align: center; }
# .todo-list { list-style: none; padding: 0; }
# .todo-item { display: flex; align-items: center; padding: 0.75rem; border-bottom: 1px solid #eee; }
# .todo-item.done .task-text { text-decoration: line-through; color: #aaa; }
# .toggle-button, .delete-button { text-decoration: none; padding: 0 0.5rem; }
# .task-text { flex-grow: 1; }
# .add-form { display: flex; margin-top: 1rem; }
# .add-form input { flex-grow: 1; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px; }
# .add-form button { padding: 0.5rem 1rem; border: none; background: #3498db; color: white; border-radius: 4px; cursor: pointer; margin-left: 0.5rem; }