Skip to main content

HTTP Methods for Forms

Web forms use HTTP methods to communicate with the server.

POST Method

Purpose: Submit data to create new resourcesUsed for form submissions that create or modify data

GET Method

Purpose: Request data from serverUsed for retrieving and displaying data
Always use POST for operations that create, update, or delete data. GET requests should be idempotent (safe to repeat without side effects).

Form Submission Flow

1

User fills form

User enters data into HTML form fields
2

Form submitted via POST

Browser sends HTTP POST request with form data
3

Flask processes request

Server validates and inserts data into database
4

Response sent

Server redirects or displays confirmation message

HTML Form Implementation

Create a form that accepts user information.
<!DOCTYPE html>
<html>
<head>
    <title>Add New User</title>
    <style>
        .form-container {
            max-width: 400px;
            margin: 50px auto;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 8px;
        }
        
        .form-group {
            margin-bottom: 15px;
        }
        
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        
        input[type="text"],
        input[type="email"] {
            width: 100%;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }
        
        button {
            background-color: #4CAF50;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        
        button:hover {
            background-color: #45a049;
        }
        
        .error {
            color: red;
            font-size: 14px;
        }
    </style>
</head>
<body>
    <div class="form-container">
        <h2>Register New User</h2>
        
        {% if error %}
        <p class="error">{{ error }}</p>
        {% endif %}
        
        <form action="/add" method="POST">
            <div class="form-group">
                <label for="name">Full Name:</label>
                <input 
                    type="text" 
                    id="name" 
                    name="name" 
                    required
                    placeholder="Enter your name"
                >
            </div>
            
            <div class="form-group">
                <label for="email">Email Address:</label>
                <input 
                    type="email" 
                    id="email" 
                    name="email" 
                    required
                    placeholder="[email protected]"
                >
            </div>
            
            <button type="submit">Add User</button>
        </form>
        
        <p><a href="/">View all users</a></p>
    </div>
</body>
</html>

Form Attributes Explained

<form action="/add" method="POST">
Specifies the URL endpoint where form data will be sent. In this case, /add route on the server.
method="POST"
Defines the HTTP method used to submit the form. POST is appropriate for creating new resources.
POST sends data in the request body (not visible in URL), making it suitable for sensitive information and large payloads.
<input type="text" name="name">
<input type="email" name="email">
The name attribute identifies each form field. These names become dictionary keys in Flask’s request.form.
<input type="text" required>
Browser-side validation that prevents form submission if field is empty.
Client-side validation is convenient but never sufficient - always validate on the server side too.

Flask Backend Implementation

Handle the form submission and database insertion.
import sqlite3
from flask import Flask, render_template, request, redirect, url_for, flash

app = Flask(__name__)
app.secret_key = 'your-secret-key-here'  # Required for flash messages

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').fetchall()
    conn.close()
    return render_template('index.html', users=users)

@app.route('/add', methods=['GET', 'POST'])
def add_user():
    if request.method == 'POST':
        # Get form data
        name = request.form['name']
        email = request.form['email']
        
        # Validate inputs
        if not name or not email:
            return render_template('add_user.html', 
                                 error='All fields are required')
        
        try:
            # Insert into database
            conn = get_db_connection()
            conn.execute(
                'INSERT INTO users (name, email) VALUES (?, ?)',
                (name, email)
            )
            conn.commit()
            conn.close()
            
            flash('User added successfully!', 'success')
            return redirect(url_for('index'))
            
        except sqlite3.IntegrityError:
            return render_template('add_user.html', 
                                 error='Email already exists')
    
    # GET request - show empty form
    return render_template('add_user.html')

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

Code Breakdown

@app.route('/add', methods=['GET', 'POST'])
def add_user():
    if request.method == 'POST':
        # Handle form submission
    return render_template('add_user.html')
The same route handles both:
  • GET: Display the form
  • POST: Process form submission
By default, routes only accept GET requests. Explicitly specify methods to accept POST.
name = request.form['name']
email = request.form['email']
Flask’s request.form is a dictionary containing all submitted form fields.Keys match the name attributes from the HTML form.
if not name or not email:
    return render_template('add_user.html', 
                         error='All fields are required')
Always validate on the server, even if you have client-side validation. Users can bypass browser validation.
try:
    conn.execute('INSERT INTO users (name, email) VALUES (?, ?)', 
                (name, email))
except sqlite3.IntegrityError:
    return render_template('add_user.html', 
                         error='Email already exists')
Catches database constraint violations, particularly UNIQUE constraint on email.
return redirect(url_for('index'))
Post-Redirect-Get (PRG) pattern: After successful POST, redirect to prevent form resubmission on page refresh.

Handling Duplicate Emails

If you try to insert the same email twice, the database operation will fail.

Why Does This Happen?

email TEXT UNIQUE NOT NULL
The UNIQUE constraint on the email column prevents duplicate entries.
# First user - Success
conn.execute('INSERT INTO users (name, email) VALUES (?, ?)',
            ('Alice', '[email protected]'))
Email doesn’t exist yet - insertion succeeds.

Proper Error Handling

try:
    conn.execute(
        'INSERT INTO users (name, email) VALUES (?, ?)',
        (name, email)
    )
    conn.commit()
    flash('User added successfully!', 'success')
    return redirect(url_for('index'))
    
except sqlite3.IntegrityError as e:
    flash('This email is already registered', 'error')
    return render_template('add_user.html', 
                         name=name, 
                         email=email)

Complete Working Example

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

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

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 DESC').fetchall()
    conn.close()
    return render_template('index.html', users=users)

@app.route('/add', methods=['GET', 'POST'])
def add_user():
    if request.method == 'POST':
        name = request.form.get('name', '').strip()
        email = request.form.get('email', '').strip().lower()
        
        # Validation
        if not name or not email:
            flash('All fields are required', 'error')
            return render_template('add_user.html', name=name, email=email)
        
        if len(name) < 2:
            flash('Name must be at least 2 characters', 'error')
            return render_template('add_user.html', name=name, email=email)
        
        if '@' not in email or '.' not in email:
            flash('Invalid email format', 'error')
            return render_template('add_user.html', name=name, email=email)
        
        # Database insertion
        try:
            conn = get_db_connection()
            conn.execute(
                'INSERT INTO users (name, email) VALUES (?, ?)',
                (name, email)
            )
            conn.commit()
            conn.close()
            
            flash(f'User {name} added successfully!', 'success')
            return redirect(url_for('index'))
            
        except sqlite3.IntegrityError:
            flash('This email is already registered', 'error')
            return render_template('add_user.html', name=name, email=email)
        
        except Exception as e:
            flash(f'An error occurred: {str(e)}', 'error')
            return render_template('add_user.html', name=name, email=email)
    
    return render_template('add_user.html')

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

Best Practices

Use Parameterized Queries

# Good ✓
conn.execute('INSERT INTO users VALUES (?, ?)', (name, email))

# Bad ✗
conn.execute(f'INSERT INTO users VALUES ("{name}", "{email}")')
Prevents SQL injection attacks

Validate All Input

  • Check for empty fields
  • Validate email format
  • Sanitize user input
  • Set length limits

Handle Errors Gracefully

try:
    # database operation
except sqlite3.IntegrityError:
    # handle constraint violation
except Exception as e:
    # handle unexpected errors

Use Flash Messages

flash('Success!', 'success')
flash('Error!', 'error')
Provide user feedback
This example uses SQLite for simplicity. In production applications, consider using an ORM like SQLAlchemy for better security, validation, and database abstraction.