Codeskill

Learn to code, step by step

Mini project Flask app forms+db

A walkthrough of a small Flask app: a shared notes list with a form to add entries, stored in SQLite via SQLAlchemy. It combines the application factory, templates, flash messages, and basic database access from earlier tutorials.

What we are building

  • GET / shows recent notes.
  • POST form adds a note with title and body.
  • SQLite stores data between restarts.
  • Tests use Flask’s test client and a temporary database.

Project layout

notes_app/
├── pyproject.toml
├── src/notes_app/
│   ├── __init__.py
│   ├── models.py
│   ├── routes.py
│   └── templates/
│       ├── base.html
│       └── index.html
└── tests/
    └── test_routes.py

Model

from datetime import datetime
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class Note(Base):
    __tablename__ = "notes"
    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str] = mapped_column()
    body: Mapped[str] = mapped_column(default="")
    created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)

App factory with database init

from flask import Flask
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from notes_app.models import Base

def create_app(test_config=None):
    app = Flask(__name__)
    app.config.from_mapping(
        SECRET_KEY="dev-only-change-me",
        SQLALCHEMY_DATABASE_URI="sqlite:///notes.db",
    )
    if test_config:
        app.config.update(test_config)

    engine = create_engine(app.config["SQLALCHEMY_DATABASE_URI"], future=True)
    Base.metadata.create_all(engine)
    SessionLocal = sessionmaker(bind=engine)

    @app.before_request
    def open_session():
        from flask import g
        g.db = SessionLocal()

    @app.teardown_request
    def close_session(exc):
        from flask import g
        g.db.close()

    from notes_app.routes import bp
    app.register_blueprint(bp)
    return app

Routes and form handling

from flask import Blueprint, flash, g, redirect, render_template, request, url_for
from notes_app.models import Note

bp = Blueprint("main", __name__)

@bp.route("/", methods=["GET", "POST"])
def index():
    if request.method == "POST":
        title = request.form.get("title", "").strip()
        body = request.form.get("body", "").strip()
        if not title:
            flash("Title is required.", "error")
        else:
            g.db.add(Note(title=title, body=body))
            g.db.commit()
            flash("Note saved.", "success")
            return redirect(url_for("main.index"))

    notes = g.db.query(Note).order_by(Note.created_at.desc()).limit(20).all()
    return render_template("index.html", notes=notes)

Template sketch

{% extends "base.html" %}
{% block content %}
<h1>Notes</h1>
<form method="post">
  <label>Title <input name="title" required></label>
  <label>Body <textarea name="body"></textarea></label>
  <button type="submit">Save</button>
</form>
<ul>
  {% for note in notes %}
    <li><strong>{{ note.title }}</strong> - {{ note.body }}</li>
  {% else %}
    <li>No notes yet.</li>
  {% endfor %}
</ul>
{% endblock %}

In your project, save that as templates/index.html. Escape user content is handled by Jinja2’s default auto-escaping for HTML.

Test the happy path

import pytest
from notes_app import create_app

@pytest.fixture
def client(tmp_path):
    db_path = tmp_path / "test.db"
    app = create_app({"TESTING": True, "SQLALCHEMY_DATABASE_URI": f"sqlite:///{db_path}"})
    return app.test_client()

def test_create_note(client):
    response = client.post("/", data={"title": "Test", "body": "Hello"}, follow_redirects=True)
    assert response.status_code == 200
    assert b"Test" in response.data

Before you call it done

  • Move secrets to environment variables.
  • Turn off Flask debug mode outside local development.
  • Add basic styling in static/style.css if you like.
  • Consider Alembic once schema changes become frequent.

That closes Going further with Python. You can write idiomatic modules, pin dependencies, test them, expose a CLI, and ship a small Flask app with forms and persistent data. The advanced tutorials goes deeper on concurrency, performance, and API design when you are ready.

PreviousMini project CLI utility