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:
| Option | Default | Effect |
|---|---|---|
force=False | False | Parse even without Content-Type: application/json |
silent=False | False | Return None on parse error instead of raising 400 |
cache=True | True | Cache 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 "", 204Request 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>")