Flask Cheatsheet
JSON APIs
Use this Flask reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
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>")