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:
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: 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)
Tip!
my_todo_app
folder and that venv
is active. Then, run python app.py
. Visit http://127.0.0.1:5000
in your browser!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: 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: 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: 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: 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; }