Skip to main content
Flask Basics Tutorial
CHAPTER 10 Beginner

Building CRUD Applications in Flask

Updated: May 14, 2026
40 min read

# CHAPTER 10

Building CRUD Applications in Flask

1. Introduction

In the previous chapter, we manipulated the database using the terminal shell. However, real users interact with databases through web browsers. In this chapter, we will synthesize routing, Jinja2 templates, HTML forms, and SQLAlchemy to build a complete CRUD (Create, Read, Update, Delete) application. We will build a web interface that allows users to seamlessly manage records in our SQLite database.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Integrate SQLAlchemy queries directly inside Flask View functions.
  • Render dynamic database data to an HTML template (Read).
  • Process HTML forms to save new records to the database (Create).
  • Query and modify existing records (Update).
  • Remove records from the database securely (Delete).

3. Beginner-Friendly Explanation

Think of your web application as a sophisticated filing system.
  • Create: A user fills out a registration form. The server takes that form and creates a new file folder in the cabinet.
  • Read: A user asks to see a list of all employees. The server opens the cabinet, counts all the folders, and writes a list on a whiteboard (The HTML Template).
  • Update: An employee gets married and changes their last name. The server finds their specific folder and rewrites the name tag.
  • Delete: An employee leaves the company. The server finds their folder and throws it in the shredder.

These four operations (CRUD) form the backbone of 90% of all software engineering.

4. Step 1: READ (Displaying Data)

Let's update our homepage to display all the users currently in the database.

In app.py:

python
1234567
@app.route('/')
def home():
    # 1. Query the database for all users
    all_users = User.query.all()
    
    # 2. Pass the list of users into the Jinja2 template
    return render_template('home.html', users=all_users)

In templates/home.html:

html
12345678910
{% extends "base.html" %}
{% block content %}
    <h1>Registered Users</h1>
    <ul>
        {% for user in users %}
            <!-- Access the database columns using dot notation! -->
            <li>{{ user.username }} ({{ user.email }})</li>
        {% endfor %}
    </ul>
{% endblock %}

5. Step 2: CREATE (Inserting Data)

We need a web form so visitors can add themselves to the database.

In app.py:

python
1234567891011121314151617181920
from flask import request, redirect, url_for

@app.route(&#039;/add', methods=['GET', 'POST'])
def add_user():
    if request.method == &#039;POST':
        # 1. Extract data from the HTML form
        uname = request.form.get(&#039;username')
        uemail = request.form.get(&#039;email')
        
        # 2. Create the Python object
        new_user = User(username=uname, email=uemail)
        
        # 3. Save to the database
        db.session.add(new_user)
        db.session.commit()
        
        # 4. Redirect back to the homepage (PRG Pattern)
        return redirect(url_for(&#039;home'))
        
    return render_template(&#039;add_user.html')

*(You will need an add_user.html template with a POST form containing name="username" and name="email" inputs).*

6. Step 3: UPDATE (Modifying Data)

To update a specific user, we must pass their ID in the URL, fetch their current data, and overwrite it.

In app.py:

python
12345678910111213141516
# The URL captures the ID of the specific user to update
@app.route(&#039;/update/<int:id>', methods=['GET', 'POST'])
def update_user(id):
    # Fetch the user, or automatically return a 404 error if they don't exist
    user_to_update = User.query.get_or_404(id)
    
    if request.method == &#039;POST':
        # Overwrite the Python object's attributes with the new form data
        user_to_update.username = request.form.get(&#039;username')
        user_to_update.email = request.form.get(&#039;email')
        
        # Commit the changes (No need to db.session.add() again)
        db.session.commit()
        return redirect(url_for(&#039;home'))
        
    return render_template(&#039;update_user.html', user=user_to_update)

7. Step 4: DELETE (Removing Data)

To securely delete data, we fetch the specific object and call .delete().

In app.py:

python
123456789
@app.route(&#039;/delete/<int:id>')
def delete_user(id):
    user_to_delete = User.query.get_or_404(id)
    
    # Shred the folder
    db.session.delete(user_to_delete)
    db.session.commit()
    
    return redirect(url_for(&#039;home'))

8. Backend Workflow: The get_or_404 Method

In Steps 3 and 4, we used User.query.get_or_404(id). Why didn't we use standard User.query.get(id)? Because if a malicious hacker tries to visit /delete/999 and user 999 does not exist, standard .get() returns None. When the code tries to delete None, Python crashes the entire server with an AttributeError. Using .get_or_404() gracefully catches the error and displays a safe "Page Not Found" screen to the hacker, keeping your server online and secure.

9. Best Practices

  • POST for Deletions: In Step 4, we made the delete route a simple GET request. In a professional application, this is a severe security risk. A web crawler (like Googlebot) visiting every link on your site would accidentally click every /delete/id link and wipe your entire database! Deletions should ALWAYS be protected behind a POST request form.

10. Common Mistakes

  • Unique Constraint Failures: In Chapter 9, we set email to unique=True. If a user fills out the Create form with an email that already exists in the database, db.session.commit() will crash with an IntegrityError. You must use try/except blocks (Chapter 15) to catch this error and show a polite message to the user ("Email already in use") instead of crashing the server.

11. Exercises

  1. 1. Explain the chronological flow of data starting from the moment a user clicks a "Delete User" link, to the moment they are redirected back to the homepage.

12. Coding Challenges

  • Challenge: Update your home.html template. Inside the loop that displays each user, add two HTML hyperlink tags (<a>). One link should point to {{ url_for('update_user', id=user.id) }} with the text "Edit". The other should point to delete_user with the text "Delete".

13. MCQs with Answers

Question 1

When building an Update route, why is it unnecessary to call db.session.add(user) before calling db.session.commit()?

Question 2

What is the critical advantage of using User.query.get_or_404(id) instead of standard .get(id) in detail or delete routes?

14. Interview Questions

  • Q: Explain the Post/Redirect/Get (PRG) pattern. Why must a developer return a redirect() rather than render_template() after successfully processing a Create or Update operation?
  • Q: Why is it considered a critical security vulnerability to execute database deletion operations (db.session.delete) via a standard GET HTTP request?

15. FAQs

Q: My database has 10,000 users. Won't User.query.all() crash my server? A: Yes! Loading 10,000 Python objects into RAM at once will cause massive memory issues. In production, developers use Pagination (e.g., User.query.paginate(page=1, per_page=20)) to only load 20 users at a time.

16. Summary

In Chapter 10, we combined all our backend knowledge to build a fully functional CRUD application. We queried data via SQLAlchemy and rendered it dynamically using Jinja2 templates (Read). We utilized HTML POST forms to extract user input and securely inserted new records (Create). We safely retrieved existing records using get_or_404 to modify attributes (Update) and remove them entirely (Delete), implementing the PRG pattern to ensure a seamless user experience.

17. Next Chapter Recommendation

Anyone can currently delete any user. We must restrict access to these powerful routes. Proceed to Chapter 11: User Authentication and Sessions.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·