CRUD Operations
CRUD stands for Create, Read, Update, and Delete — the four fundamental operations every data-driven application performs on its records, and with Flask-SQLAlchemy each one is just a few lines of Python.
Learn CRUD Operations in our free Flask course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Flask course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll insert new rows, query existing ones, modify records in place, and remove them, building the complete lifecycle of database-backed data.
Create means inserting a new row: build a model instance, db.session.add() it, then db.session.commit() . Read means querying: query.all() for every row, query.get(id) for one by primary key.
The code below creates a few products, then reads them back:
query.get(1) looks up the row whose primary key is 1. In a route you'd often use get_or_404(1) so a missing record returns a clean 404 page. Run flask run to serve routes built on this.
Update is wonderfully simple in SQLAlchemy: fetch the object, change its attributes in plain Python, and commit() . The ORM detects the change and writes it — you never type an SQL UPDATE .
Because the object is tracked by the session, simply assigning product.price = 39.99 stages an update; commit() writes it to the database.
Delete removes a row: fetch the object, pass it to db.session.delete() , then commit() . Here it is wired into a real route using get_or_404 :
Delete routes use POST rather than GET so a page cannot accidentally remove data just by being loaded. Try flask run and submit a form that posts to this route.
Complete the code below. Replace each ___ to create a note, update it, then delete it.
Every change needs db.session.commit() . Changing an attribute or calling delete() only stages the change until you commit.
❌ AttributeError: 'NoneType' object has no attribute ...
query.get(id) returns None when no row matches. Use get_or_404(id) in routes, or check for None before using the result.
Lesson 12 complete — you can manage data end to end!
You now create, read, update, and delete records with Flask-SQLAlchemy, and you know how get_or_404 protects routes against missing rows.
🚀 Up next: Database Migrations — evolve your schema safely as your models change.
Practice quiz
What does CRUD stand for?
- Copy, Read, Use, Delete
- Create, Read, Update, Delete
- Cache, Run, Update, Drop
- Connect, Resolve, Undo, Deploy
Answer: Create, Read, Update, Delete. CRUD is Create, Read, Update, and Delete — the four basic data operations.
Which two calls insert a new row in SQLAlchemy?
- db.session.add(obj) then db.session.commit()
- db.insert(obj)
- obj.save()
- db.session.create(obj)
Answer: db.session.add(obj) then db.session.commit(). Stage with db.session.add(obj), then persist with db.session.commit().
How do you fetch one row by its primary key?
- Model.query.first(id)
- Model.find(id)
- Model.query.get(id)
- Model.query.all(id)
Answer: Model.query.get(id). Model.query.get(id) returns the row with that primary key or None.
How do you update a record with SQLAlchemy?
- call db.update(obj)
- run a raw UPDATE statement
- obj.refresh()
- change the attribute then db.session.commit()
Answer: change the attribute then db.session.commit(). Edit the object's attributes in Python, then commit; the ORM writes the change.
Which call removes a row?
- db.session.delete(obj) then commit
- obj.remove()
- Model.query.drop(obj)
- db.session.pop(obj)
Answer: db.session.delete(obj) then commit. Pass the object to db.session.delete(obj), then commit to remove the row.
What does Model.query.get_or_404(id) do when no row matches?
- returns None
- raises a 404 Not Found
- returns an empty list
- creates the row
Answer: raises a 404 Not Found. get_or_404 returns the row, or aborts with a clean 404 if it does not exist.
Why must you call db.session.commit() after changes?
- to print the row
- to open the database
- without it changes are only staged, not saved
- to create the table
Answer: without it changes are only staged, not saved. add, delete, and attribute edits are staged until commit() writes them.
Why do delete routes typically use POST instead of GET?
- GET is deprecated
- POST is faster
- POST encrypts data
- so a loaded page cannot accidentally delete data
Answer: so a loaded page cannot accidentally delete data. GET requests can fire from links/prefetch; POST avoids accidental deletes.
What does Product.query.all() return?
- a list of all rows
- the first row only
- a count
- the table name
Answer: a list of all rows. query.all() returns every row of the model as a list.
Which method filters rows by a column value?
- query.where(name='x')
- query.filter_by(name='x')
- query.match('x')
- query.select('x')
Answer: query.filter_by(name='x'). filter_by(name='x') returns a query filtered on that column; add .first() or .all().