Skip to main content

Why DELETE is Critical in CRUD

The DELETE operation completes the CRUD lifecycle and is essential for data management.

Data Hygiene

Removes obsolete, duplicate, or irrelevant data to maintain database performance

User Control

Allows users to manage their data, exercise “right to be forgotten”, and remove unwanted content

Storage Management

Prevents unnecessary data accumulation and reduces storage costs

Compliance

Meets legal requirements like GDPR for data deletion
Without DELETE, applications cannot fully manage their data lifecycle, leading to bloated databases and poor user experience.

Common Security Issues

Using links for deletion creates serious security vulnerabilities.

The Problem with GET Requests

<!-- DO NOT USE THIS -->
<a href="/delete/{{ user['id'] }}">Delete</a>
Problems:
  • Uses GET method for state-changing operation
  • Can be triggered by web crawlers
  • Vulnerable to CSRF attacks
  • Can be executed accidentally
  • Browser prefetching may trigger deletion
HTTP Method Guidelines:
  • GET: Read data (idempotent, safe)
  • POST: Create/modify data (non-idempotent)
  • DELETE: Remove data (idempotent)

Implementing Safe Deletion

Step 1: HTML Template with Form

<!DOCTYPE html>
<html>
<head>
    <title>Users List</title>
    <style>
        table {
            border-collapse: collapse;
            width: 100%;
            margin-top: 20px;
        }
        
        th, td {
            border: 1px solid #ddd;
            padding: 12px;
            text-align: left;
        }
        
        th {
            background-color: #4CAF50;
            color: white;
        }
        
        .delete-form {
            display: inline;
        }
        
        .delete-btn {
            background-color: #f44336;
            color: white;
            border: none;
            padding: 6px 12px;
            cursor: pointer;
            border-radius: 4px;
        }
        
        .delete-btn:hover {
            background-color: #da190b;
        }
    </style>
</head>
<body>
    <h1>Registered Users</h1>
    
    {% if message %}
    <div class="alert">{{ message }}</div>
    {% endif %}
    
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            {% for user in users %}
            <tr>
                <td>{{ user['id'] }}</td>
                <td>{{ user['name'] }}</td>
                <td>{{ user['email'] }}</td>
                <td>
                    <form action="/delete/{{ user['id'] }}" 
                          method="POST" 
                          class="delete-form"
                          onsubmit="return confirmDelete('{{ user['name'] }}')">
                        <button type="submit" class="delete-btn">Delete</button>
                    </form>
                </td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
    
    <script>
        function confirmDelete(userName) {
            return confirm(`Are you sure you want to delete user "${userName}"?\n\nThis action cannot be undone.`);
        }
    </script>
</body>
</html>

Step 2: Flask Route Handler

import sqlite3
from flask import Flask, render_template, request, redirect, url_for, flash

app = Flask(__name__)
app.secret_key = 'your-secret-key-here'

def get_db_connection():
    conn = sqlite3.connect('users.db')
    conn.row_factory = sqlite3.Row
    return conn

@app.route('/')
def index():
    conn = get_db_connection()
    users = conn.execute('SELECT * FROM users ORDER BY id').fetchall()
    conn.close()
    return render_template('index.html', users=users)

@app.route('/delete/<int:user_id>', methods=['POST'])
def delete_user(user_id):
    try:
        conn = get_db_connection()
        
        # Get user info before deletion for logging
        user = conn.execute('SELECT * FROM users WHERE id = ?', 
                          (user_id,)).fetchone()
        
        if user is None:
            flash('User not found', 'error')
            conn.close()
            return redirect(url_for('index'))
        
        # Delete the user
        conn.execute('DELETE FROM users WHERE id = ?', (user_id,))
        conn.commit()
        conn.close()
        
        flash(f'User "{user["name"]}" deleted successfully', 'success')
        
    except sqlite3.Error as e:
        flash(f'Error deleting user: {str(e)}', 'error')
    
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(debug=True)

Key Implementation Details

@app.route('/delete/<int:user_id>', methods=['POST'])
By specifying methods=['POST'], Flask will reject GET requests to this endpoint, preventing accidental or malicious deletions.
@app.route('/delete/<int:user_id>', methods=['POST'])
def delete_user(user_id):
The <int:user_id> ensures only integer IDs are accepted. Invalid types return 404 automatically.
user = conn.execute('SELECT * FROM users WHERE id = ?', 
                   (user_id,)).fetchone()

if user is None:
    flash('User not found', 'error')
    return redirect(url_for('index'))
Always verify the record exists before attempting deletion to provide meaningful feedback.
flash(f'User "{user["name"]}" deleted successfully', 'success')
Provide clear confirmation to the user about what action was performed.

Enhanced Safety Features

1. Client-Side Confirmation

<style>
    .modal {
        display: none;
        position: fixed;
        z-index: 1000;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0,0,0,0.5);
    }
    
    .modal-content {
        background-color: white;
        margin: 15% auto;
        padding: 20px;
        border-radius: 8px;
        width: 400px;
        text-align: center;
    }
    
    .modal-buttons {
        margin-top: 20px;
    }
    
    .confirm-btn {
        background-color: #f44336;
        color: white;
        padding: 10px 20px;
        border: none;
        border-radius: 4px;
        margin: 0 5px;
        cursor: pointer;
    }
    
    .cancel-btn {
        background-color: #ccc;
        color: black;
        padding: 10px 20px;
        border: none;
        border-radius: 4px;
        margin: 0 5px;
        cursor: pointer;
    }
</style>

<div id="deleteModal" class="modal">
    <div class="modal-content">
        <h3>Confirm Deletion</h3>
        <p id="deleteMessage"></p>
        <div class="modal-buttons">
            <button class="cancel-btn" onclick="closeModal()">Cancel</button>
            <button class="confirm-btn" onclick="confirmDeletion()">Delete</button>
        </div>
    </div>
</div>

<script>
    let pendingDeleteForm = null;
    
    function showDeleteModal(userName, form) {
        event.preventDefault();
        pendingDeleteForm = form;
        document.getElementById('deleteMessage').textContent = 
            `Are you sure you want to delete user "${userName}"?`;
        document.getElementById('deleteModal').style.display = 'block';
    }
    
    function closeModal() {
        document.getElementById('deleteModal').style.display = 'none';
        pendingDeleteForm = null;
    }
    
    function confirmDeletion() {
        if (pendingDeleteForm) {
            pendingDeleteForm.submit();
        }
    }
</script>

<!-- Usage in table -->
<form action="/delete/{{ user['id'] }}" 
      method="POST" 
      onsubmit="showDeleteModal('{{ user['name'] }}', this); return false;">
    <button type="submit" class="delete-btn">Delete</button>
</form>

2. CSRF Protection

from flask_wtf.csrf import CSRFProtect

app = Flask(__name__)
app.secret_key = 'your-secret-key-here'
csrf = CSRFProtect(app)

@app.route('/delete/<int:user_id>', methods=['POST'])
@csrf.protect()  # Automatic CSRF validation
def delete_user(user_id):
    # deletion logic
    pass
CSRF (Cross-Site Request Forgery) tokens prevent malicious websites from triggering actions on your site using a user’s authenticated session.

3. Authorization Check

from flask_login import login_required, current_user

@app.route('/delete/<int:user_id>', methods=['POST'])
@login_required
def delete_user(user_id):
    # Check if user has permission to delete
    if not current_user.is_admin and current_user.id != user_id:
        flash('You do not have permission to delete this user', 'error')
        return redirect(url_for('index'))
    
    # Prevent users from deleting themselves
    if current_user.id == user_id:
        flash('You cannot delete your own account', 'error')
        return redirect(url_for('index'))
    
    # Proceed with deletion
    conn = get_db_connection()
    conn.execute('DELETE FROM users WHERE id = ?', (user_id,))
    conn.commit()
    conn.close()
    
    flash('User deleted successfully', 'success')
    return redirect(url_for('index'))

Soft Delete Alternative

For cases where you need to preserve data or enable “undo”, implement soft deletes.
ALTER TABLE users ADD COLUMN deleted_at DATETIME;
Soft deletes allow you to:
  • Implement “Restore” functionality
  • Maintain referential integrity
  • Keep audit trails
  • Comply with data retention policies

Best Practices Checklist

1

Use POST method

Never use GET requests for deletion operations
Forms with method="POST" prevent accidental execution
2

Implement confirmation

Always ask users to confirm destructive actions
JavaScript confirmation dialogs or modal windows
3

Add CSRF protection

Prevent cross-site request forgery attacks
Use flask_wtf.csrf or similar libraries
4

Verify authorization

Ensure users can only delete data they own
Check user permissions before deletion
5

Validate existence

Check if record exists before attempting deletion
Provide meaningful error messages
6

Provide feedback

Inform users about the result of their action
Use flash messages for success/error states
7

Consider soft deletes

For sensitive data, mark as deleted instead of removing
Allows data recovery and audit trails
Never implement deletion via simple links or GET requests. This is a critical security vulnerability that can lead to data loss through:
  • Accidental clicks
  • Web crawler indexing
  • Browser prefetching
  • CSRF attacks
In production applications, consider implementing:
  • Audit logging for all deletions
  • Time-based data retention policies
  • Batch deletion with transaction support
  • Cascade deletion rules for related records