Python Flask Tutorial: Full-Featured Web App Part 4 - Database with Flask-SQLAlchemy
Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.
Install and configure Flask-SQLAlchemy by setting `SQLALCHEMY_DATABASE_URI` (SQLite for development, swap the URI later for Postgres).
Briefing
Flask-SQLAlchemy turns a Flask app’s database into Python classes—letting developers create real users and posts backed by SQLite in development, with the option to swap to Postgres in production without rewriting the core data-access code. The setup starts by installing Flask-SQLAlchemy, configuring a database URI (using an SQLite file like `site.db` during development), and creating a `DB` instance tied to the Flask app.
With the database connection in place, the next step is defining the data model. A `User` model becomes a database table with columns for `id` (an integer primary key), `username` (string, max length 20, unique, not nullable), `email` (string, max length 120, unique, not nullable), `image_file` (string, max length 20, not nullable, with a default like `default.jpg`), and `password` (string, max length 60, not nullable). A `__repr__` method is added so printed `User` objects show key fields—username, email, and image—without exposing the password.
A `Post` model is defined similarly, representing another table. It includes `id` (primary key), `title` (string, max length 100, not nullable), `date_posted` (datetime, not nullable, defaulting to `date.utcnow` in UTC for consistency), and `content` (text, not nullable). Like `User`, it also gets a `__repr__` method that prints a concise summary (title and date) rather than the full content.
The crucial relationship ties the two models together. A one-to-many association is created: one `User` can have many `Post` entries, while each `Post` belongs to a single author. In SQLAlchemy terms, the `User` model gets a `posts` relationship with a `backref='author'` and `lazy=True`, enabling convenient navigation from a post back to its author via `post.author`—even though `author` is not a physical column in the `Post` table. The `Post` model also defines a `user_id` column as a foreign key referencing `user.id`, with `nullable=False` to enforce that every post has an author.
Once models are defined, the database schema is created using `DB.create_all()`, which generates the SQLite file and tables on disk. Sample data is then inserted through the Flask app context using `DB.session.add()` and `DB.session.commit()`: two users are created, followed by two posts linked to the first user via `user_id`. Querying is demonstrated with `User.query.all()`, `User.query.first()`, `filter_by(...)`, `query.get(id)`, and relationship access like `user.posts`.
To keep the development workflow clean, the tutorial also shows how to reset everything: `DB.drop_all()` removes all tables and rows, and `DB.create_all()` recreates the schema from scratch. After the reset, querying users and posts returns empty lists, confirming a fresh database state for the next stage of the series—refactoring the app into a package and moving models into separate files.
Cornell Notes
Flask-SQLAlchemy maps database tables to Python classes so developers can define `User` and `Post` models with columns, defaults, constraints, and readable `__repr__` output. The `User` model includes unique `username` and `email`, a default `image_file` (e.g., `default.jpg`), and a hashed `password` field sized for common hashing outputs. The `Post` model stores `title`, `content`, and a UTC `date_posted` default using `date.utcnow`. A one-to-many relationship links them: `User.posts` returns a list of posts, while `Post.user_id` enforces authorship and `post.author` is available via `backref='author'`. The workflow uses `DB.create_all()` to build the schema, `DB.session.add()`/`commit()` to insert rows, and `DB.drop_all()` to reset to a clean slate.
Why configure `SQLALCHEMY_DATABASE_URI` with an SQLite file during development, and what changes when moving to Postgres later?
What constraints and defaults make the `User` model practical for a real app?
How does the `Post` model ensure consistent timestamps and required fields?
What exactly creates the one-to-many link between users and posts, and how does `post.author` work?
What is the difference between adding objects to the session and committing them to the database?
How do `DB.drop_all()` and `DB.create_all()` support iterative development?
Review Questions
- How do `unique=True` and `nullable=False` on `User.username` and `User.email` affect database behavior when inserting duplicate or missing values?
- Why is `default=date.utcnow` written without parentheses, and what would change if it were written as `default=date.utcnow()`?
- In the user-post relationship, which field actually stores the link in the database, and which attribute provides the convenient reverse lookup?
Key Points
- 1
Install and configure Flask-SQLAlchemy by setting `SQLALCHEMY_DATABASE_URI` (SQLite for development, swap the URI later for Postgres).
- 2
Create a `DB` instance tied to the Flask app (`DB = SQLAlchemy(app)`) before defining models.
- 3
Define `User` and `Post` as SQLAlchemy model classes with explicit column types, length limits, and nullability rules.
- 4
Use `__repr__` methods to make debugging output readable without leaking sensitive fields like passwords.
- 5
Model the one-to-many relationship with `User.posts = DB.relationship(..., backref='author')` and `Post.user_id = DB.ForeignKey('user.id')`.
- 6
Insert data using `DB.session.add()` followed by `DB.session.commit()` to persist changes.
- 7
Reset the schema during development with `DB.drop_all()` and rebuild it with `DB.create_all()` to start from a clean database.