Flask-SQLAlchemy
Python
database operations
web development
ORM

How to delete a record by ID in Flask-SQLAlchemy

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Deleting a row by primary key is a standard Flask-SQLAlchemy task, but it is worth doing in a way that is explicit about missing records and transaction boundaries. The basic pattern is to load the model instance, delete it from the session, and commit the transaction. From there, the details depend on whether you want a plain function, an API route, or bulk behavior.

The Normal Deletion Pattern

With modern Flask-SQLAlchemy built on SQLAlchemy 2 style sessions, the clearest approach is db.session.get followed by db.session.delete.

python
1from flask import Flask, jsonify
2from flask_sqlalchemy import SQLAlchemy
3
4app = Flask(__name__)
5app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///example.db"
6db = SQLAlchemy(app)
7
8class User(db.Model):
9    id = db.Column(db.Integer, primary_key=True)
10    name = db.Column(db.String(80), nullable=False)
11
12@app.route("/users/<int:user_id>", methods=["DELETE"])
13def delete_user(user_id):
14    user = db.session.get(User, user_id)
15    if user is None:
16        return jsonify({"error": "User not found"}), 404
17
18    db.session.delete(user)
19    db.session.commit()
20    return jsonify({"status": "deleted", "id": user_id}), 200

This approach is easy to read and gives you a chance to return a 404 when the ID does not exist.

Why Load the Record First?

Some developers look for a one-line delete by ID, but loading the object first has a few advantages:

  • you can handle the missing-record case cleanly
  • SQLAlchemy can run mapper events and cascades as expected
  • the code is easy to extend with authorization or logging checks

If your application needs to verify ownership before deletion, this pattern gives you a clear place to do that.

For example:

python
1@app.route("/projects/<int:project_id>", methods=["DELETE"])
2def delete_project(project_id):
3    project = db.session.get(Project, project_id)
4    if project is None:
5        return {"error": "Project not found"}, 404
6
7    # Example policy check would go here.
8    db.session.delete(project)
9    db.session.commit()
10    return {"message": "Project deleted"}, 200

Handling Errors Safely

A delete should be part of a transaction. If something fails during commit, roll the session back.

python
1from sqlalchemy.exc import SQLAlchemyError
2
3@app.route("/users/<int:user_id>", methods=["DELETE"])
4def delete_user_safe(user_id):
5    user = db.session.get(User, user_id)
6    if user is None:
7        return {"error": "User not found"}, 404
8
9    try:
10        db.session.delete(user)
11        db.session.commit()
12        return {"status": "deleted"}, 200
13    except SQLAlchemyError:
14        db.session.rollback()
15        return {"error": "Delete failed"}, 500

This becomes more important when foreign keys, cascades, or database constraints are involved.

Bulk Deletes Are Different

If you need to delete many rows by a filter, SQLAlchemy also supports delete statements. That is a different use case from deleting one record by ID.

python
1from sqlalchemy import delete
2
3stmt = delete(User).where(User.name == "inactive")
4db.session.execute(stmt)
5db.session.commit()

This can be efficient, but it bypasses some instance-level ORM behavior. For a single record deletion where you care about application logic, loading the instance is usually the safer choice.

Older Examples You May Still See

Older Flask-SQLAlchemy examples often use User.query.get(user_id). That pattern exists in many codebases, but newer SQLAlchemy guidance favors session-based APIs such as db.session.get(User, user_id).

If you are maintaining legacy code, both styles may appear. For new code, prefer the session-based form because it matches current SQLAlchemy direction more closely.

Common Pitfalls

A common mistake is calling db.session.delete and forgetting db.session.commit. Without the commit, the row is marked for deletion in the session but not actually removed from the database.

Another mistake is assuming the ID exists. Trying to delete None will fail, so always check the lookup result first.

Developers also sometimes use bulk delete operations when they really need model-level behavior, cascades, or validation. Those cases are better served by loading the instance first.

Finally, if a commit fails, remember to call db.session.rollback(). A broken session should not be reused without cleanup.

Summary

  • The standard pattern is db.session.get, then db.session.delete, then db.session.commit.
  • Check for None so missing IDs return a proper 404.
  • Wrap commits in error handling when database constraints might fail.
  • Use bulk delete statements only when you intentionally want statement-style behavior.
  • Prefer session-based APIs in modern Flask-SQLAlchemy code.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.