backend beginner

Flask + PostgreSQL CRUD Operations

min read Flask Vibe Team

Flask + PostgreSQL CRUD Operations

Learn how to build CRUD operations the Flask Vibe way: with raw SQL and psycopg2.

Why No ORM?

ORMs hide what's happening. When you write:

# SQLAlchemy (ORM)
user = User.query.filter_by(email=email).first()

You don't know: - What SQL is being generated - If indexes are being used - How many queries are running (N+1 problem)

Compare with Flask Vibe approach:

# psycopg2 (raw SQL)
with get_db() as conn:
    with conn.cursor() as cur:
        cur.execute(
            "SELECT * FROM users WHERE email = %s LIMIT 1",
            (email,)
        )
        user = cur.fetchone()

Crystal clear. You see exactly what's happening.

Database Connection Helper

# utils/db.py
import psycopg2
from psycopg2.extras import RealDictCursor
from contextlib import contextmanager
from config import Config

@contextmanager
def get_db():
    """Get a database connection, commit on success, rollback and close on error."""
    conn = psycopg2.connect(
        host=Config.DB_HOST,
        port=Config.DB_PORT,
        database=Config.DB_NAME,
        user=Config.DB_USER,
        password=Config.DB_PASSWORD
    )
    conn.cursor_factory = RealDictCursor
    try:
        yield conn
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()  # Always close — use connection pooling in production

For production, replace this with the pooled version from Connection Pooling with psycopg2. Pooling reuses connections instead of opening a new one per request.

CREATE: Adding Records

from flask import Flask, request, redirect
from utils.db import get_db

@app.route("/users/create", methods=["POST"])
def create_user():
    name = request.form.get("name")
    email = request.form.get("email")

    with get_db() as conn:
        with conn.cursor() as cur:
            cur.execute(
                "INSERT INTO users (name, email) VALUES (%s, %s) RETURNING id",
                (name, email)
            )
            user_id = cur.fetchone()['id']
            conn.commit()

    return redirect(f"/users/{user_id}")

READ: Fetching Records

@app.route("/users")
def list_users():
    with get_db() as conn:
        with conn.cursor() as cur:
            cur.execute("SELECT * FROM users ORDER BY created_at DESC")
            users = cur.fetchall()
    return render_template("users.html", users=users)

@app.route("/users/<int:user_id>")
def show_user(user_id):
    with get_db() as conn:
        with conn.cursor() as cur:
            cur.execute("SELECT * FROM users WHERE id = %s", (user_id,))
            user = cur.fetchone()

    if not user:
        return "User not found", 404

    return render_template("user.html", user=user)

UPDATE: Modifying Records

@app.route("/users/<int:user_id>/edit", methods=["POST"])
def update_user(user_id):
    name = request.form.get("name")
    email = request.form.get("email")

    with get_db() as conn:
        with conn.cursor() as cur:
            cur.execute(
                "UPDATE users SET name = %s, email = %s, updated_at = NOW() WHERE id = %s",
                (name, email, user_id)
            )
            conn.commit()

    return redirect(f"/users/{user_id}")

DELETE: Removing Records

@app.route("/users/<int:user_id>/delete", methods=["POST"])
def delete_user(user_id):
    with get_db() as conn:
        with conn.cursor() as cur:
            cur.execute("DELETE FROM users WHERE id = %s", (user_id,))
            conn.commit()

    return redirect("/users")

AI Prompt That Generated This

"Create Flask CRUD routes for a users table with columns: id, name, email, created_at, updated_at. Use psycopg2 with parameterized queries for SQL injection protection. Include error handling for database connections."

AI loves simple, direct code. No ORM confusion, no magic methods to hallucinate.

Next Steps

Was this helpful?

Get More Flask Vibe Tutorials

Join 1,000+ developers getting weekly Flask tips and AI-friendly code patterns.

No spam. Unsubscribe anytime.