Flask SQLAlchemy Models
Learn to define and use Flask SQLAlchemy models in this Python web development step-by-step tutorial. Master the core concept with hands-on code, compare approaches, and troubleshoot common issues—all within our progressive track.
Focus: use flask sqlalchemy for models
You've built a Flask app that serves pages, handles forms, and maybe even talks to a database with raw SQL. But as your app grows, you start to feel the pain: SQL strings scattered across your views, manual mapping between rows and Python objects, and little bugs that only appear when you mistype a column name. This lesson solves that problem by introducing Flask-SQLAlchemy, the ORM that lets you define your database schema as Python classes—models—and interact with your data through clean, object-oriented code. By the end, you'll be able to use Flask SQLAlchemy for models with confidence, and you'll see why this is a game-changer for any Python web developer.
The problem this lesson solves
Raw SQL in a Flask app is like building a house with a hammer and no nails—it works, but it's slow, error-prone, and hard to maintain. Consider a typical view that fetches a user by email:
# app.py (no ORM — painful approach)
import sqlite3
from flask import Flask, g
app = Flask(__name__)
DATABASE = 'app.db'
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
return db
@app.route('/user/<email>')
def user_by_email(email):
cur = get_db().execute(
"SELECT id, name, email FROM users WHERE email = ?", (email,))
row = cur.fetchone()
if row is None:
return "User not found", 404
user = {"id": row[0], "name": row[1], "email": row[2]}
return f"User: {user['name']} ({user['email']})"
This code is brittle: you must remember column names, handle tuple indexing manually, and you'll repeat these patterns for every table. Worse, it's not Pythonic—you're thinking in rows, not objects. Flask-SQLAlchemy eliminates this by mapping Python classes to database tables. The pain of raw SQL—query strings, object mapping, connection management—is replaced with a clean, declarative API that feels like writing normal Python.
Core concept / mental model
Think of a database as a collection of tables (like spreadsheets). A model is a Python class that represents one table. Each instance of that class is a row in the table, and each attribute of the instance is a column in that row. When you use Flask SQLAlchemy for models, you're essentially creating a translation layer between your Python objects and the relational database. This is called an Object-Relational Mapper (ORM).
Imagine you have a library. The card catalog (schemas) tells you where books are. The books themselves are rows. A model is like a librarian who knows how to find, add, or remove books without you needing to know the exact shelf numbers—you just say "give me all mystery books" and the librarian does the SQL for you.
In Flask-SQLAlchemy, you create a db object that holds the ORM's core. Then you define models by subclassing db.Model. Each model has columns defined using db.Column, with types like db.Integer, db.String, and db.DateTime. Relationships between tables are modeled with db.relationship, letting you navigate from one model to another as if they were linked objects.
How it works step by step
Here's the high-level flow of how Flask SQLAlchemy works under the hood when you use it in a Flask app:
- Initialize the extension — Create a
db = SQLAlchemy()instance in your application. This binds the ORM to your Flask app. - Define models — Create classes inheriting from
db.Model. Each class represents a table. Define columns withdb.Columnand specify types, primary keys, and constraints. - Create the database — Call
db.create_all()to generate tables from your model definitions. This is done once, typically in a shell or initialization script. - Interact with data — Use the session (
db.session) to add, query, update, and delete records. The session tracks changes and commits them as a transaction to the database. - Query with ORM — Use
Model.queryto fetch data. It returns objects, not tuples, so you can access attributes directly. - Commit transactions — After adding or modifying records, call
db.session.commit()to persist changes. Without a commit, changes are lost.
This flow replaces raw SQL's manual connection handling and cursor management. The ORM handles connection pooling, quoting, and escaping—reducing the risk of SQL injection attacks.
Hands-on walkthrough
Let's build a small Flask app that uses Flask-SQLAlchemy for models. First, install the library:
pip install flask flask-sqlalchemy
Now create app.py with a simple User model:
# app.py
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# Define the User model
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return f'<User {self.username}>'
# Create tables within the app context
with app.app_context():
db.create_all()
# Routes to use the model
@app.route('/users', methods=['POST'])
def create_user():
data = request.get_json()
user = User(username=data['username'], email=data['email'])
db.session.add(user)
db.session.commit()
return jsonify({'id': user.id, 'username': user.username}), 201
@app.route('/users/<int:user_id>')
def get_user(user_id):
user = User.query.get(user_id)
if user is None:
return jsonify({'error': 'Not found'}), 404
return jsonify({'id': user.id, 'username': user.username, 'email': user.email})
if __name__ == '__main__':
app.run(debug=True)
Run the app, then test it with curl:
curl -X POST http://127.0.0.1:5000/users -H "Content-Type: application/json" -d '{"username":"alice","email":"alice@example.com"}'
# Output: {"id":1,"username":"alice"}
curl http://127.0.0.1:5000/users/1
# Output: {"id":1,"username":"alice","email":"alice@example.com"}
The model acts as a Python object, and Flask-SQLAlchemy does all the heavy lifting. Notice how we never wrote a single SQL statement.
Now, let's extend the example with a relationship. Imagine a Post model that belongs to a User:
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
user = db.relationship('User', backref=db.backref('posts', lazy=True))
Notice the foreign key syntax and the relationship. Now you can do user.posts to get all posts by that user, and post.user to get the author. This is the magic of using Flask SQLAlchemy for models—complex joins become simple attribute access.
Compare options / when to choose what
Flask-SQLAlchemy isn't the only way to handle models in Flask. Here's a comparison with other popular approaches:
| Approach | Pros | Cons | Best for |
|---|---|---|---|
| Flask-SQLAlchemy | Tight Flask integration, easy setup, full ORM | Slightly abstracted — you lose some SQL control | Most Flask apps, especially small to medium size |
| Raw SQL (sqlite3/psycopg2) | Full control, no magic | Verbose, error-prone, repetitive | Simple scripts or performance-critical queries |
| SQLAlchemy Core | Low-level ORM, explicit SQL | More boilerplate, less beginner-friendly | Apps needing fine-grained control without full ORM |
| Other ORMs (Peewee, SQLObject) | Lighter, alternative syntax | Less Flask integration, smaller ecosystem | Microframeworks or a preference for minimalism |
When to choose Flask-SQLAlchemy: If you're building a standard CRUD app, need relationships, and want to move fast, this is your tool. When to avoid it: If you're only doing trivial lookups, or if you need to write highly optimized complex queries that ORMs mangle, consider SQLAlchemy Core or raw SQL. However, for 95% of development, Flask-SQLAlchemy is the pragmatic choice.
Troubleshooting & edge cases
Even with an ORM, things go wrong. Here are common pitfalls and how to fix them:
ModuleNotFoundError: No module named 'flask_sqlalchemy'– Did you install Flask-SQLAlchemy? Runpip install flask-sqlalchemyin your virtual environment.RuntimeError: Working outside of application context– This happens when you calldb.create_all()outside a request or app context. Wrap it inwith app.app_context():as shown above.sqlalchemy.exc.IntegrityError: UNIQUE constraint failed– You tried to insert a duplicate username or email. Add error handling to catch unique violations, or check existence first.AttributeError: 'NoneType' object has no attribute 'username'– The query returnedNonebecause you usedgetwith a non-existent ID. Always check forNonebefore accessing attributes.- **
Flask-SQLAlchemyuses adbobject, but you forgot to setSQLALCHEMY_TRACK_MODIFICATIONS– not an error, just a warning: Disable it withapp.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Falseto silence it.
Edge case — lazy loading: When accessing user.posts, SQLAlchemy executes a query behind the scenes. If you're outside an app context (e.g., in a script without app.app_context()), you may encounter issues. Keep all model interactions inside request handlers or app context blocks.
Edge case — multiple databases: If you need to connect to more than one database, Flask-SQLAlchemy supports binds, but it adds complexity. For most apps, one database is enough.
What you learned & what's next
You now understand the core idea behind using Flask SQLAlchemy for models: defining database tables as Python classes, querying them with object-oriented methods, and managing relationships easily. You've completed a practical exercise that showed how to create, query, and relate models. You can now apply this in your own projects to reduce code duplication and improve safety.
Next step: In the next lesson of this Python web development track, you'll learn how to handle database migrations with Flask-Migrate, so you can evolve your schema without losing data. With models under your belt, you'll be ready to build real-world applications with proper data persistence.
What you should remember from this lesson:
- Models map to tables – Each Python class becomes a database table, and each attribute is a column.
- The
dbobject is your bridge – It handles connections, sessions, and everything in between. - Use
db.create_all()in an app context – Or you'll hit the “working outside of application context” error. - Querying returns objects, not tuples – This is the biggest productivity win.
- Relationships connect models – Foreign keys and
db.relationshipmake joins trivial. - Always commit you session – Uncommitted changes are lost.
Now, go ahead and try adding a new model to your own Flask app. You'll feel the difference immediately!
Practice recap
Mini exercise: Extend the example by adding a Post model with a foreign key to User. Create a route that accepts a POST request with a title and content, associates it with a user, and returns the post data. Then, test querying user.posts to see the relationship in action. This will solidify your understanding of how Flask-SQLAlchemy models and relationships work together.
Common mistakes
- Forgetting to run
db.create_all()after defining models, leading to 'no such table' errors. - Calling
db.create_all()outside an app context, causing 'RuntimeError: Working outside of application context'. - Not checking for
Nonewhen usingModel.query.get(id), leading to AttributeError on missing records. - Ignoring unique constraints and not catching
IntegrityErrorwhen duplicates are inserted.
Variations
- Use
db.Column(db.Text)for long text fields instead ofdb.Stringwith fixed length. - Define models in separate modules and use
db.init_app(app)to avoid circular imports. - Use
db.relationshipwithlazy='dynamic'for scalable querying of related records.
Real-world use cases
- A blog platform where posts and authors are models, enabling easy queries for all posts by a user.
- An e-commerce API with Product and Order models, using relationships to manage inventory and checkout.
- A task management app where User and Task models allow assigning tasks and filtering by status.
Key takeaways
- Flask-SQLAlchemy lets you define database tables as Python classes, eliminating raw SQL boilerplate.
- Models make queries return objects, so you access columns as attributes—no more tuple indexing.
- Relationships like
db.relationshipturn complex joins into simple attribute access. - Always use
db.session.commit()to persist changes; otherwise, data isn't saved. - Run
db.create_all()inside an app context to create tables from your models. - Using Flask-SQLAlchemy is the standard choice for Flask apps of most sizes, offering a great balance of power and simplicity.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.