Responses and JSON API

Introduction

View functions return strings, tuples, dicts, or Response objects. Flask sets status codes and headers accordingly. This chapter covers HTML and JSON responses, jsonify, make_response, common HTTP status codes, and a simple {code, message, data} API envelope teams often adopt.

Prerequisites

String and Tuple Responses

Code explanation:

  • (body, status) tuple sets status without custom headers
  • (body, status, headers_dict) third element adds headers

HTML Responses

python
@app.route("/page")
def page():
    return "<h1>Hello</h1>", 200, {"Content-Type": "text/html; charset=utf-8"}

Prefer templates for real pages—chapter 8.

jsonify and Dict Returns

python
from flask import Flask, jsonify
 
app = Flask(__name__)
 
@app.route("/api/user/1")
def get_user():
    return jsonify({"id": 1, "name": "Alice"})
 
@app.route("/api/status")
def status():
    return {"status": "ok", "version": 1}

Both produce application/json.

Custom status with jsonify:

python
return jsonify({"error": "Not found"}), 404

make_response

Full control over body, status, headers, cookies:

python
from flask import Flask, make_response
 
app = Flask(__name__)
 
@app.route("/set-theme")
def set_theme():
    resp = make_response({"theme": "dark"})
    resp.set_cookie("theme", "dark", httponly=True, samesite="Lax")
    resp.headers["X-App-Version"] = "1.0"
    return resp

Clear cookie:

python
resp.set_cookie("theme", "", expires=0)

Common HTTP Status Codes

CodeMeaningTypical use
200OKSuccessful GET
201CreatedPOST created resource
204No ContentDELETE success
400Bad RequestValidation failed
401UnauthorizedMissing/invalid auth
403ForbiddenAuthenticated but not allowed
404Not FoundUnknown route or resource
500Server ErrorUnhandled exception

Example validation:

python
from flask import Flask, request, jsonify
 
app = Flask(__name__)
 
@app.route("/api/items", methods=["POST"])
def create_item():
    data = request.get_json(silent=True) or {}
    name = data.get("name", "").strip()
    if not name:
        return jsonify({"error": "name is required"}), 400
    return jsonify({"id": 1, "name": name}), 201

Unified API Envelope

Team convention:

python
def api_ok(data=None, message="success", code=0):
    return jsonify({"code": code, "message": message, "data": data})
 
def api_error(message, http_status=400, code=1):
    return jsonify({"code": code, "message": message, "data": None}), http_status
 
@app.route("/api/profile")
def profile():
    return api_ok({"name": "Alice"})

Clients always parse code, message, data—business errors may use code != 0 with HTTP 200 (document team rules clearly).

Tip

Pick One Error Style

Mixing raw HTTP errors and envelope errors confuses API clients—standardize in project docs.

Content Negotiation (Brief)

Same route, different format:

python
from flask import Flask, request, jsonify
 
app = Flask(__name__)
 
@app.route("/resource/1")
def resource():
    if request.accept_mimes.accept_json:
        return jsonify({"id": 1})
    return "<p>Resource 1</p>", 200, {"Content-Type": "text/html"}

Most APIs are JSON-only.

Error Handlers for APIs

python
@app.errorhandler(404)
def api_not_found(e):
    if request.path.startswith("/api/"):
        return jsonify({"error": "not found"}), 404
    return "Not found", 404

FAQ

jsonify vs return dict?

Equivalent for JSON in modern Flask—jsonify adds explicit JSON response type.

Chinese or emoji in JSON?

Return str; Flask JSON encoder handles UTF-8; set JSON_AS_ASCII = False in config if needed.

CORS errors in browser?

Browser blocks cross-origin—use Flask-CORS extension for API projects.

Return list at top level?

Valid JSON—return [{"id": 1}] works.

201 without body?

return "", 201 or return jsonify({}), 201.

Pretty-print JSON in dev?

app.config["JSONIFY_PRETTYPRINT_REGULAR"] = True—disable in production.