Flask Cheatsheet

JSON APIs

Use this Flask reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Returning JSON

from flask import Flask, jsonify, request

app = Flask(__name__)

@app.route("/api/items")
def list_items():
    items = [{"id": 1, "name": "Widget"}, {"id": 2, "name": "Gadget"}]
    return jsonify(items)                 # 200 application/json

@app.route("/api/items/<int:item_id>")
def get_item(item_id):
    item = find_item(item_id)
    if item is None:
        return jsonify({"error": "Not found"}), 404
    return jsonify(item)

Flask 2.2+ auto-serializes dict and list returns:

@app.get("/api/user")
def get_user():
    return {"id": 1, "name": "Alice"}    # Content-Type: application/json

@app.get("/api/users")
def get_users():
    return [{"id": 1}, {"id": 2}]        # list also works

Receiving JSON

@app.post("/api/items")
def create_item():
    data = request.get_json()
    if data is None:
        return {"error": "JSON body required"}, 400

    name = data.get("name")
    if not name:
        return {"error": "name is required"}, 422

    item = {"id": 99, "name": name}
    return jsonify(item), 201

get_json() options:

OptionDefaultEffect
force=FalseFalseParse even without Content-Type: application/json
silent=FalseFalseReturn None on parse error instead of raising 400
cache=TrueTrueCache parsed result on request object

Standard API Response Patterns

def success(data, status=200):
    return jsonify({"data": data, "ok": True}), status

def error(message, status=400, details=None):
    body = {"ok": False, "error": message}
    if details:
        body["details"] = details
    return jsonify(body), status

@app.get("/api/users/<int:id>")
def get_user(id):
    user = db.session.get(User, id)
    if not user:
        return error("User not found", 404)
    return success(user.to_dict())

REST CRUD Example

items = {}

@app.get("/api/items")
def list_items():
    return jsonify(list(items.values()))

@app.post("/api/items")
def create_item():
    data = request.get_json(silent=True) or {}
    if "name" not in data:
        return {"error": "name required"}, 422
    new_id = max(items, default=0) + 1
    items[new_id] = {"id": new_id, "name": data["name"]}
    return jsonify(items[new_id]), 201, {"Location": f"/api/items/{new_id}"}

@app.get("/api/items/<int:item_id>")
def get_item(item_id):
    item = items.get(item_id)
    return jsonify(item) if item else ({"error": "not found"}, 404)

@app.put("/api/items/<int:item_id>")
def replace_item(item_id):
    if item_id not in items:
        return {"error": "not found"}, 404
    data = request.get_json(silent=True) or {}
    items[item_id] = {"id": item_id, "name": data.get("name", "")}
    return jsonify(items[item_id])

@app.patch("/api/items/<int:item_id>")
def update_item(item_id):
    if item_id not in items:
        return {"error": "not found"}, 404
    data = request.get_json(silent=True) or {}
    items[item_id].update({k: v for k, v in data.items() if k != "id"})
    return jsonify(items[item_id])

@app.delete("/api/items/<int:item_id>")
def delete_item(item_id):
    items.pop(item_id, None)
    return "", 204

Request Validation

Manual validation pattern:

def validate(data: dict, schema: dict) -> list[str]:
    """schema: {field: type}"""
    errors = []
    for key, typ in schema.items():
        if key not in data:
            errors.append(f"{key} is required")
        elif not isinstance(data[key], typ):
            errors.append(f"{key} must be {typ.__name__}")
    return errors

@app.post("/api/users")
def create_user():
    data = request.get_json(silent=True) or {}
    errors = validate(data, {"username": str, "age": int})
    if errors:
        return {"errors": errors}, 422
    # proceed

With marshmallow:

pip install marshmallow
from marshmallow import Schema, fields, ValidationError

class UserSchema(Schema):
    username = fields.Str(required=True)
    email = fields.Email(required=True)
    age = fields.Int(load_default=None)

schema = UserSchema()

@app.post("/api/users")
def create_user():
    try:
        data = schema.load(request.get_json(silent=True) or {})
    except ValidationError as err:
        return {"errors": err.messages}, 422
    return {"id": 1, **data}, 201

Error Handling for APIs

from flask import jsonify
from werkzeug.exceptions import HTTPException

@app.errorhandler(HTTPException)
def http_error(e):
    return jsonify({"error": e.name, "description": e.description}), e.code

@app.errorhandler(404)
def not_found(e):
    return jsonify({"error": "Not Found"}), 404

@app.errorhandler(422)
def unprocessable(e):
    return jsonify({"error": "Unprocessable Entity"}), 422

@app.errorhandler(Exception)
def server_error(e):
    app.logger.exception(e)
    return jsonify({"error": "Internal Server Error"}), 500

Content Negotiation

@app.route("/api/report")
def report():
    data = {"rows": 100, "status": "done"}
    best = request.accept_mimetypes.best_match(["application/json", "text/csv"])
    if best == "text/csv":
        csv = "rows,status\n100,done"
        return csv, 200, {"Content-Type": "text/csv"}
    return jsonify(data)

Pagination Pattern

Flask-SQLAlchemy 3.x style — db.paginate() with a select() statement (the legacy Model.query.paginate() still exists but Model.query is deprecated):

from sqlalchemy import select

@app.get("/api/posts")
def list_posts():
    page = request.args.get("page", 1, type=int)
    per_page = min(request.args.get("per_page", 20, type=int), 100)

    pagination = db.paginate(
        select(Post).order_by(Post.created_at.desc()),
        page=page, per_page=per_page, error_out=False,
    )
    return jsonify({
        "items": [p.to_dict() for p in pagination.items],
        "total": pagination.total,
        "page": pagination.page,
        "pages": pagination.pages,
        "has_next": pagination.has_next,
        "has_prev": pagination.has_prev,
    })

Authentication Headers

import functools

def require_api_key(f):
    @functools.wraps(f)
    def decorated(*args, **kwargs):
        key = request.headers.get("X-Api-Key")
        if key != app.config["API_KEY"]:
            return jsonify({"error": "Unauthorized"}), 401
        return f(*args, **kwargs)
    return decorated

@app.get("/api/secret")
@require_api_key
def secret():
    return {"data": "top secret"}

JWT bearer token check:

def get_token():
    auth = request.headers.get("Authorization", "")
    if auth.startswith("Bearer "):
        return auth[7:]
    return None

JSON Configuration

Flask 2.3 removed the JSON_SORT_KEYS / JSONIFY_PRETTYPRINT_REGULAR / JSONIFY_MIMETYPE config keys. Configure the JSON provider on app.json instead:

app.json.sort_keys = False              # preserve insertion order (default True)
app.json.compact = False                # pretty-print; None = compact unless debug
app.json.mimetype = "application/json"  # response Content-Type
app.json.ensure_ascii = False           # emit UTF-8 instead of \uXXXX escapes

Custom serialization (e.g., Decimal, dataclasses) via a custom provider:

from decimal import Decimal
from flask.json.provider import DefaultJSONProvider

class MyJSONProvider(DefaultJSONProvider):
    @staticmethod
    def default(o):
        if isinstance(o, Decimal):
            return str(o)
        return DefaultJSONProvider.default(o)

app.json = MyJSONProvider(app)

Async Views (Flask 2+)

import asyncio
import aiohttp

@app.get("/api/async-data")
async def async_data():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.example.com/data") as resp:
            data = await resp.json()
    return jsonify(data)

Requires pip install "flask[async]" (installs asgiref).

flask-restful (Extension)

Maintenance mode. flask-restful accepts only critical fixes, and its own docs deprecate reqparse (recommending marshmallow instead). For new APIs prefer plain Flask views + marshmallow/pydantic, or actively maintained alternatives (flask-smorest, APIFlask). Shown here because it is still common in existing codebases.

pip install flask-restful
from flask_restful import Api, Resource

api = Api(app)

class UserResource(Resource):
    def get(self, user_id):
        return {"id": user_id, "name": "Alice"}

    def post(self):
        data = request.get_json(silent=True) or {}
        if "name" not in data:
            return {"error": "name is required"}, 422
        return {"name": data["name"]}, 201

    def delete(self, user_id):
        return "", 204

api.add_resource(UserResource, "/users", "/users/<int:user_id>")