Toolmingo
Guides10 min read

Flask Cheat Sheet: The Complete Quick Reference

A complete Flask cheat sheet — routing, request/response, Blueprints, SQLAlchemy, authentication, testing, and deployment with copy-ready Python code.

The Flask patterns you need every day — routing, request handling, Blueprints, database integration, auth, and testing — with copy-ready code in every section.

Quick reference

Task Code
Install pip install flask
Run dev server flask run or flask --app app run
Debug mode flask run --debug
GET route @app.get("/path")
POST route @app.post("/path")
Multiple methods @app.route("/path", methods=["GET","POST"])
URL parameter @app.get("/users/<int:user_id>")
Build URL url_for("view_name", id=1)
JSON request body request.get_json()
Form data request.form["field"]
Query string request.args.get("q", "")
Return JSON jsonify({"key": "value"})
Return with status jsonify(data), 201
Redirect redirect(url_for("index"))
Abort abort(404)
Render template render_template("index.html", name=name)
Set cookie resp.set_cookie("name", "value")
Read cookie request.cookies.get("name")
App factory def create_app(): app = Flask(__name__); ...
Blueprint bp = Blueprint("auth", __name__)
Register blueprint app.register_blueprint(bp, url_prefix="/auth")
Config from object app.config.from_object("config.ProductionConfig")
Config from env app.config.from_prefixed_env("FLASK")
Request context with app.test_request_context():

Installation and minimal app

pip install flask
# app.py
from flask import Flask

app = Flask(__name__)

@app.get("/")
def index():
    return "Hello, Flask!"

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

Run it:

flask --app app run --debug
# or
python app.py

Routing

from flask import Flask, url_for

app = Flask(__name__)

# Basic GET (shorthand decorator)
@app.get("/")
def index():
    return "Home"

# Multiple HTTP methods
@app.route("/submit", methods=["GET", "POST"])
def submit():
    if request.method == "POST":
        return "Submitted"
    return "Show form"

# URL parameters
@app.get("/users/<int:user_id>")
def get_user(user_id: int):
    return f"User {user_id}"

@app.get("/posts/<string:slug>")
def get_post(slug: str):
    return f"Post: {slug}"

@app.get("/files/<path:filename>")
def serve_file(filename: str):
    return f"File: {filename}"

# URL converters: string (default), int, float, path, uuid

# Building URLs (always use url_for — never hardcode)
with app.app_context():
    print(url_for("index"))              # "/"
    print(url_for("get_user", user_id=5)) # "/users/5"
    print(url_for("index", page=2))      # "/?page=2"
    print(url_for("index", _external=True)) # "http://localhost/"

Request object

from flask import request

@app.route("/data", methods=["GET", "POST"])
def data():
    # Query string: GET /data?q=hello&page=2
    q     = request.args.get("q", "")
    page  = request.args.get("page", 1, type=int)

    # Form data (application/x-www-form-urlencoded or multipart)
    name  = request.form.get("name")
    email = request.form["email"]              # raises 400 if missing

    # JSON body (application/json)
    body  = request.get_json()                 # dict or None
    body  = request.get_json(force=True)       # parse regardless of content-type
    body  = request.get_json(silent=True)      # return None instead of 400

    # File upload
    file  = request.files.get("photo")
    if file and file.filename:
        file.save(f"/uploads/{file.filename}")

    # Headers and cookies
    token = request.headers.get("Authorization")
    user  = request.cookies.get("user_id")

    # Request meta
    ip     = request.remote_addr
    method = request.method                    # "GET"
    path   = request.path                      # "/data"
    host   = request.host                      # "localhost:5000"

    return "ok"

Response

from flask import jsonify, make_response, redirect, url_for, abort

# Plain text / HTML
@app.get("/hello")
def hello():
    return "Hello"                  # 200 OK, text/html

# JSON response
@app.get("/api/user")
def api_user():
    return jsonify({"id": 1, "name": "Alice"})  # 200, application/json

# Custom status code
@app.post("/api/users")
def create_user():
    user = {"id": 2, "name": "Bob"}
    return jsonify(user), 201

# Tuple shorthand: (body, status) or (body, status, headers)
@app.get("/error")
def error():
    return {"error": "not found"}, 404

# make_response — for setting cookies, headers
@app.get("/login")
def login():
    resp = make_response(redirect(url_for("index")))
    resp.set_cookie("session", "abc123", httponly=True, secure=True, samesite="Lax")
    return resp

# Delete cookie
@app.get("/logout")
def logout():
    resp = make_response(redirect(url_for("index")))
    resp.delete_cookie("session")
    return resp

# Abort early (triggers error handler)
@app.get("/admin")
def admin():
    if not is_authenticated():
        abort(401)
    return "Admin panel"

# Custom response headers
@app.get("/csv")
def csv_file():
    data = "name,age\nAlice,30"
    resp = make_response(data)
    resp.headers["Content-Type"] = "text/csv"
    resp.headers["Content-Disposition"] = "attachment; filename=data.csv"
    return resp

Error handlers

from flask import jsonify

@app.errorhandler(404)
def not_found(e):
    return jsonify(error=str(e)), 404

@app.errorhandler(401)
def unauthorized(e):
    return jsonify(error="Unauthorized"), 401

@app.errorhandler(500)
def server_error(e):
    return jsonify(error="Internal server error"), 500

# Catch all exceptions
@app.errorhandler(Exception)
def handle_exception(e):
    # Pass through HTTP errors
    from werkzeug.exceptions import HTTPException
    if isinstance(e, HTTPException):
        return jsonify(error=e.description), e.code
    return jsonify(error="Internal server error"), 500

Blueprints (modular apps)

Blueprints split a large app into smaller modules.

# auth/routes.py
from flask import Blueprint, jsonify, request

bp = Blueprint("auth", __name__, url_prefix="/auth")

@bp.post("/login")
def login():
    data = request.get_json()
    # ... verify credentials
    return jsonify(token="jwt-token-here")

@bp.post("/register")
def register():
    data = request.get_json()
    # ... create user
    return jsonify(message="registered"), 201
# users/routes.py
from flask import Blueprint, jsonify

bp = Blueprint("users", __name__, url_prefix="/users")

@bp.get("/")
def list_users():
    return jsonify(users=[])

@bp.get("/<int:user_id>")
def get_user(user_id: int):
    return jsonify(id=user_id)
# app.py (application factory)
from flask import Flask

def create_app(config_name="development"):
    app = Flask(__name__)
    app.config.from_object(f"config.{config_name.title()}Config")

    from auth.routes import bp as auth_bp
    from users.routes import bp as users_bp

    app.register_blueprint(auth_bp)
    app.register_blueprint(users_bp)

    return app

Application factory pattern

project/
├── app/
│   ├── __init__.py      ← create_app()
│   ├── models.py
│   ├── auth/
│   │   └── routes.py
│   └── users/
│       └── routes.py
├── config.py
├── run.py
└── requirements.txt
# config.py
import os

class Config:
    SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret-change-me")
    SQLALCHEMY_TRACK_MODIFICATIONS = False

class DevelopmentConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = "sqlite:///dev.db"

class ProductionConfig(Config):
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"]

class TestingConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
# app/__init__.py
from flask import Flask
from .extensions import db, migrate

def create_app(config_name="development"):
    app = Flask(__name__)
    app.config.from_object(f"config.{config_name.title()}Config")

    db.init_app(app)
    migrate.init_app(app, db)

    from .auth.routes import bp as auth_bp
    from .users.routes import bp as users_bp
    app.register_blueprint(auth_bp)
    app.register_blueprint(users_bp)

    return app
# app/extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

db = SQLAlchemy()
migrate = Migrate()

Flask-SQLAlchemy

pip install flask-sqlalchemy flask-migrate
# app/models.py
from datetime import datetime, timezone
from .extensions import db

class User(db.Model):
    __tablename__ = "users"

    id         = db.Column(db.Integer, primary_key=True)
    email      = db.Column(db.String(120), unique=True, nullable=False)
    name       = db.Column(db.String(80), nullable=False)
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))

    posts = db.relationship("Post", back_populates="author", lazy="select")

    def to_dict(self):
        return {"id": self.id, "email": self.email, "name": self.name}


class Post(db.Model):
    __tablename__ = "posts"

    id        = db.Column(db.Integer, primary_key=True)
    title     = db.Column(db.String(200), nullable=False)
    body      = db.Column(db.Text, nullable=False)
    author_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)

    author = db.relationship("User", back_populates="posts")

CRUD with SQLAlchemy 2.x style:

from flask import Blueprint, jsonify, request
from app.extensions import db
from app.models import User

bp = Blueprint("users", __name__, url_prefix="/users")

# CREATE
@bp.post("/")
def create_user():
    data = request.get_json()
    user = User(email=data["email"], name=data["name"])
    db.session.add(user)
    db.session.commit()
    return jsonify(user.to_dict()), 201

# READ all
@bp.get("/")
def list_users():
    users = db.session.execute(db.select(User)).scalars().all()
    return jsonify([u.to_dict() for u in users])

# READ one
@bp.get("/<int:user_id>")
def get_user(user_id: int):
    user = db.get_or_404(User, user_id)
    return jsonify(user.to_dict())

# UPDATE
@bp.put("/<int:user_id>")
def update_user(user_id: int):
    user = db.get_or_404(User, user_id)
    data = request.get_json()
    user.name = data.get("name", user.name)
    db.session.commit()
    return jsonify(user.to_dict())

# DELETE
@bp.delete("/<int:user_id>")
def delete_user(user_id: int):
    user = db.get_or_404(User, user_id)
    db.session.delete(user)
    db.session.commit()
    return "", 204

# Filtering and ordering
@bp.get("/search")
def search_users():
    q = request.args.get("q", "")
    users = db.session.execute(
        db.select(User)
          .where(User.name.ilike(f"%{q}%"))
          .order_by(User.created_at.desc())
          .limit(20)
    ).scalars().all()
    return jsonify([u.to_dict() for u in users])

Migrations:

flask db init      # first time only — creates migrations/
flask db migrate -m "add users table"
flask db upgrade
flask db downgrade

Jinja2 templates

from flask import render_template

@app.get("/profile/<name>")
def profile(name: str):
    return render_template("profile.html", name=name, items=[1, 2, 3])
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head><title>{% block title %}My App{% endblock %}</title></head>
<body>
  {% block content %}{% endblock %}
</body>
</html>

<!-- templates/profile.html -->
{% extends "base.html" %}

{% block title %}{{ name }}'s Profile{% endblock %}

{% block content %}
  <h1>Hello, {{ name | capitalize }}!</h1>

  {% if items %}
    <ul>
      {% for item in items %}
        <li>{{ item }}</li>
      {% endfor %}
    </ul>
  {% else %}
    <p>No items.</p>
  {% endif %}
{% endblock %}

Common Jinja2 filters: {{ text | upper }}, {{ text | truncate(50) }}, {{ date | datetimeformat }}, {{ value | default("N/A") }}, {{ html | safe }} (use carefully).

Middleware and request hooks

import time
from flask import g, request

# Runs before each request
@app.before_request
def start_timer():
    g.start = time.perf_counter()

# Runs after each request (has access to response)
@app.after_request
def add_timing_header(response):
    elapsed = time.perf_counter() - g.start
    response.headers["X-Response-Time"] = f"{elapsed:.3f}s"
    return response

# Runs after request even if exception occurred
@app.teardown_request
def teardown(exc):
    db.session.remove()  # typical SQLAlchemy teardown

# g is a request-scoped namespace (reset per request)
@app.before_request
def load_user():
    token = request.headers.get("Authorization", "").removeprefix("Bearer ")
    g.current_user = verify_token(token) if token else None

REST API with JWT auth

pip install flask flask-sqlalchemy PyJWT
# Simple JWT Bearer auth
import jwt
import datetime
from functools import wraps
from flask import request, jsonify, current_app, g

def create_token(user_id: int) -> str:
    payload = {
        "sub": user_id,
        "iat": datetime.datetime.utcnow(),
        "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=24),
    }
    return jwt.encode(payload, current_app.config["SECRET_KEY"], algorithm="HS256")

def login_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.headers.get("Authorization", "")
        if not auth.startswith("Bearer "):
            return jsonify(error="Missing token"), 401
        token = auth.removeprefix("Bearer ")
        try:
            payload = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])
            g.user_id = payload["sub"]
        except jwt.ExpiredSignatureError:
            return jsonify(error="Token expired"), 401
        except jwt.InvalidTokenError:
            return jsonify(error="Invalid token"), 401
        return f(*args, **kwargs)
    return decorated

# Usage
@app.post("/login")
def login():
    data = request.get_json()
    user = verify_credentials(data["email"], data["password"])
    if not user:
        return jsonify(error="Invalid credentials"), 401
    return jsonify(token=create_token(user.id))

@app.get("/me")
@login_required
def me():
    user = db.get_or_404(User, g.user_id)
    return jsonify(user.to_dict())

Testing

pip install pytest
# tests/conftest.py
import pytest
from app import create_app
from app.extensions import db as _db

@pytest.fixture
def app():
    app = create_app("testing")
    with app.app_context():
        _db.create_all()
        yield app
        _db.drop_all()

@pytest.fixture
def client(app):
    return app.test_client()

@pytest.fixture
def runner(app):
    return app.test_cli_runner()
# tests/test_users.py
def test_create_user(client):
    resp = client.post("/users/", json={"email": "a@b.com", "name": "Alice"})
    assert resp.status_code == 201
    data = resp.get_json()
    assert data["email"] == "a@b.com"

def test_get_user_not_found(client):
    resp = client.get("/users/999")
    assert resp.status_code == 404

def test_list_users_empty(client):
    resp = client.get("/users/")
    assert resp.status_code == 200
    assert resp.get_json() == []

def test_update_user(client):
    # create first
    post_resp = client.post("/users/", json={"email": "b@b.com", "name": "Bob"})
    user_id = post_resp.get_json()["id"]

    put_resp = client.put(f"/users/{user_id}", json={"name": "Robert"})
    assert put_resp.status_code == 200
    assert put_resp.get_json()["name"] == "Robert"

def test_auth_required(client):
    resp = client.get("/me")
    assert resp.status_code == 401

Run tests:

pytest -v
pytest tests/test_users.py -k "test_create"
pytest --cov=app --cov-report=html

CLI commands

# app/cli.py
import click
from flask import current_app
from .extensions import db
from .models import User

def register_commands(app):
    @app.cli.command("seed-db")
    def seed_db():
        """Seed the database with test data."""
        user = User(email="admin@example.com", name="Admin")
        db.session.add(user)
        db.session.commit()
        click.echo("Seeded database.")

    @app.cli.command("create-admin")
    @click.argument("email")
    @click.argument("name")
    def create_admin(email, name):
        """Create an admin user."""
        user = User(email=email, name=name)
        db.session.add(user)
        db.session.commit()
        click.echo(f"Admin {email} created.")
flask seed-db
flask create-admin admin@example.com "Admin User"

Deployment

Gunicorn (Linux/Mac)

pip install gunicorn
gunicorn "app:create_app()" -w 4 -b 0.0.0.0:8000

Waitress (Windows)

pip install waitress
waitress-serve --port=8000 "app:create_app()"

Docker

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV FLASK_ENV=production
CMD ["gunicorn", "app:create_app()", "-w", "4", "-b", "0.0.0.0:8000"]

Environment variables

export FLASK_APP=app
export FLASK_ENV=production
export SECRET_KEY=your-secret-key
export DATABASE_URL=postgresql://user:pass@localhost/mydb

Or use a .env file with python-dotenv:

pip install python-dotenv
# run.py
from dotenv import load_dotenv
load_dotenv()
from app import create_app

app = create_app()

Common mistakes

Mistake Problem Fix
app.run() in production Single-threaded, not secure Use gunicorn/waitress
Hardcoded SECRET_KEY Security risk if code is public Load from environment variable
debug=True in production Exposes interactive debugger (RCE risk) Set FLASK_ENV=production
Not using url_for() Hardcoded URLs break on route changes Always use url_for("view_name")
Using request.json Raises 400 if content-type is wrong Use request.get_json(silent=True)
db.session without commit Changes not persisted Call db.session.commit()
No error handler Werkzeug HTML errors leak in API Add @app.errorhandler(Exception)
Missing url_prefix on blueprint Routes collide across blueprints Set url_prefix on register_blueprint

Flask vs Django vs FastAPI

Feature Flask Django FastAPI
Philosophy Micro-framework, batteries optional Batteries included Async-first, type-driven
Learning curve Low Medium Low-medium
Built-in ORM No (use SQLAlchemy) Yes (Django ORM) No (use SQLAlchemy/Tortoise)
Auto-generated docs No No (DRF optional) Yes (Swagger/Redoc)
Request validation Manual / Flask-WTF Forms / DRF serializers Pydantic automatic
Async support Limited (Flask 2+) Limited (ASGI optional) Native async
Admin panel No Built-in No
Best for Small APIs, prototypes, custom apps Large full-stack apps High-perf APIs, ML services

FAQ

When should I use Flask instead of FastAPI or Django? Flask is ideal when you want full control over your stack, are building a small-to-medium API, or are adding a web layer to an existing project. FastAPI is better if you need automatic validation and OpenAPI docs. Django is better for full-stack apps with admin, ORM, and auth bundled in.

How do I handle CORS in Flask? Install flask-cors and register it on the app:

from flask_cors import CORS
CORS(app, origins=["https://yourfrontend.com"])
# or for development
CORS(app)

What is g and when should I use it? g is a request-scoped namespace that lasts for exactly one request. Use it to store data that multiple functions need during the same request — like the current user after authentication — without passing it as function arguments.

How do I stream a large response?

from flask import stream_with_context, Response

@app.get("/stream")
def stream():
    def generate():
        for i in range(1000):
            yield f"data: {i}\n\n"
    return Response(stream_with_context(generate()), mimetype="text/event-stream")

How do I handle file uploads safely?

from werkzeug.utils import secure_filename
import os

ALLOWED = {"png", "jpg", "pdf"}

@app.post("/upload")
def upload():
    file = request.files.get("file")
    if not file or "." not in file.filename:
        abort(400)
    ext = file.filename.rsplit(".", 1)[1].lower()
    if ext not in ALLOWED:
        abort(400)
    filename = secure_filename(file.filename)
    file.save(os.path.join(app.config["UPLOAD_FOLDER"], filename))
    return jsonify(filename=filename), 201

How do I add rate limiting?

pip install flask-limiter
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(get_remote_address, app=app, default_limits=["200/day", "50/hour"])

@app.get("/api/search")
@limiter.limit("10/minute")
def search():
    return jsonify(results=[])

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools