Unit 6 Project: Tiny Flask Notes App
Assemble routes, templates, forms, and validation
In this lesson
Explain it like I’m 5
A notes app is a page that shows a list and a form that adds one more item to that list. Add a note, it appears; restart the server with no storage, and the list starts empty again.
The whole app, end to end
Two routes do the work: / shows the notes, and /add receives the form and stores a new one. The notes live in a plain list in memory — simple to start with, and enough to learn the flow.
# app.py
from flask import Flask, request, redirect, render_template
app = Flask(__name__)
notes = [] # in-memory; resets when the server restarts
@app.route("/")
def index():
return render_template("index.html", notes=notes)
@app.route("/add", methods=["POST"])
def add():
note = request.form.get("note", "").strip()
if note:
notes.append(note)
return redirect("/")
Keep the logic out of the route
Notice the real decisions — clean the text, reject blanks, build the list HTML — are simple Python. Pulling them into their own functions makes them testable without a running server, exactly like the file-organizer project in Unit 5.
import html
def add_note(notes, text):
text = text.strip()
if text:
notes.append(text)
return notes
def render_notes(notes):
if not notes:
return "<p>No notes yet.</p>"
items = "".join(f"<li>{html.escape(n)}</li>" for n in notes)
return f"<ul>{items}</ul>"
notes = []
add_note(notes, " Buy milk ")
add_note(notes, "")
print(render_notes(notes))
<ul><li>Buy milk</li></ul>
Why notes vanish on restart
The list lives in memory, so it’s wiped every time the server restarts. Persisting notes (to a file or database) is the natural next step — but the app works without it, which keeps the project unblocked.
Finish add_note so it ignores blank notes, then confirm the empty-state message shows when nothing valid has been added yet.
def add_note(notes, text):
text = text.strip()
# TODO: append text to notes only if it is non-blank
return notes
def render_notes(notes):
if not notes:
return "No notes yet"
return ", ".join(notes)
notes = []
add_note(notes, " ")
print(render_notes(notes))
Guard with if text: before appending. The only note added is all spaces, so it's rejected and the list stays empty.
def add_note(notes, text):
text = text.strip()
if text:
notes.append(text)
return notes
def render_notes(notes):
if not notes:
return "No notes yet"
return ", ".join(notes)
notes = []
add_note(notes, " ")
print(render_notes(notes))
No notes yet
Common mistake: Expecting in-memory notes to survive a restart
The notes persist while the server runs, so it feels like they’re saved.
A plain list is wiped on restart. To keep notes, write them to a file or database — that’s the next improvement beyond this project.
Common mistake: Letting blank notes into the list
Submitting an empty form still calls the add route.
Strip and check the value before appending, so empty or whitespace-only submissions are ignored.
Common mistake: Putting all the HTML inside route functions
Returning HTML strings directly is the quickest way to see something.
Move the markup into templates and keep routes thin. Your views should fetch data and render, not assemble big HTML strings.
Which route shows the list of notes?
/ renders the page listing the notes; /add only receives the form.
Where should the page’s HTML live?
Keep markup in templates and routes thin — the view supplies data and renders.
Why do notes disappear after a restart without storage?
An in-memory list resets when the process restarts; persisting needs a file or database.
Mini exercise (hard)
Build the notes core: add_note(notes, text) appends a stripped, non-blank note and returns the list; render_notes(notes) returns a <ul> of <li> items, or <p>No notes yet.</p> when the list is empty.
Do it here. Finish the code below and press Run to try your answer right in the browser — no setup needed.
def add_note(notes, text):
# TODO: strip text; append only if non-blank; return notes
return notes
def render_notes(notes):
# TODO: return "<p>No notes yet.</p>" when empty,
# otherwise a <ul> of <li> items
return ""
notes = []
add_note(notes, "Buy milk")
add_note(notes, " ")
add_note(notes, "Walk dog")
print(render_notes(notes))
add_note: strip, then if text: notes.append(text). render_notes: return the no-notes paragraph when empty, else join <li> items inside a <ul>.
def add_note(notes, text):
text = text.strip()
if text:
notes.append(text)
return notes
def render_notes(notes):
if not notes:
return "<p>No notes yet.</p>"
items = "".join(f"<li>{n}</li>" for n in notes)
return f"<ul>{items}</ul>"
notes = []
add_note(notes, "Buy milk")
add_note(notes, " ")
add_note(notes, "Walk dog")
print(render_notes(notes))
<ul><li>Buy milk</li><li>Walk dog</li></ul>
n = []
assert render_notes(n) == "<p>No notes yet.</p>", "empty list should show the no-notes message"
add_note(n, " Hi ")
assert n == ["Hi"], "a real note should be stripped and added"
add_note(n, " ")
assert n == ["Hi"], "a blank note must be rejected"
assert render_notes(n) == "<ul><li>Hi</li></ul>", "render the notes as <li> items inside a <ul>"
print("✓ Notes app core works!")