Khi một view Flask cần trả về nhiều hơn một chuỗi hay một dict — ví dụ status 201 kèm header Location, hoặc một cookie đi kèm response — hãy dùng make_response(). Hàm này bọc nội dung thành đối tượng Response để bạn chỉnh status, header và cookie trước khi trả về, thay vì nhận status 200 mặc định.

Vấn đề

Mặc định, Flask tự bọc giá trị view trả về thành response 200. Cách này đủ cho endpoint chỉ trả dữ liệu, nhưng bế tắc khi bạn cần thêm thứ đi kèm. Endpoint tạo user dưới đây trả đúng dữ liệu, nhưng client không biết tài nguyên mới nằm ở đâu, cũng không nhận được header để lần theo log:

from flask import Flask, jsonify, request
app = Flask(__name__)
@app.post("/users")
def create_user():
data = request.get_json(silent=True) or {}
if not data.get("name"):
return jsonify({"error": "Thiếu trường name"}), 400
# Trả đúng dữ liệu, nhưng vẫn là 200, không có header Location
return jsonify({"id": 42, "name": data["name"]})

Kiểu trả tuple (body, status, headers) giải quyết được status và header đơn giản, nhưng khi cần gắn thêm cookie, bạn phải tự viết header Set-Cookie bằng tay — dễ sai định dạng và khó đọc khi một response cần cả status, header lẫn cookie.

Giải pháp

make_response() nhận nội dung trả về và trả lại đối tượng Response để bạn chỉnh sửa trước khi return. Tạo tài nguyên mới thì đặt status 201 và header Location trỏ tới tài nguyên vừa tạo, đồng thời gắn header tùy chỉnh để lần theo log:

from flask import Flask, jsonify, make_response, request
app = Flask(__name__)
@app.post("/users")
def create_user():
data = request.get_json(silent=True) or {}
if not data.get("name"):
return jsonify({"error": "Thiếu trường name"}), 400
response = make_response(jsonify({"id": 42, "name": data["name"]}))
response.status_code = 201
response.headers["Location"] = "/api/users/42"
response.headers["X-Request-Id"] = "abc-123"
return response

Với cookie, set_cookie() và delete_cookie() làm đúng phần việc khó: định dạng header, hạn dùng và các cờ bảo mật. Đừng tự nối chuỗi Set-Cookie thủ công khi đã có sẵn phương thức này:

@app.post("/theme")
def set_theme():
response = make_response(jsonify({"theme": "dark"}))
response.set_cookie("theme", "dark", httponly=True,
samesite="Lax", max_age=30 * 24 * 3600)
return response
@app.post("/theme/logout")
def clear_theme():
response = make_response(jsonify({"theme": None}))
response.delete_cookie("theme")
return response

Lưu ý: delete_cookie() không xóa cookie ngay mà gửi lại cookie đó với thời gian hết hạn đã qua, để trình duyệt tự loại bỏ. Nếu endpoint chỉ cần đổi status, tuple (body, status) vẫn đủ gọn — đừng lạm dụng make_response() cho mọi view.

Áp dụng vào dự án của bạn

Rà lại các endpoint và áp dụng make_response() đúng chỗ: endpoint tạo tài nguyên trả 201 kèm Location, endpoint đăng nhập/đăng xuất gắn hoặc xóa cookie, và response cần header X-Request-Id để đối chiếu log khi sự cố xảy ra. Trước khi đưa lên production, kiểm chứng bằng test client của Flask — assert cả status, header và cookie chứ đừng chỉ assert body:

client = app.test_client()
resp = client.post("/users", json={"name": "Linh"})
assert resp.status_code == 201
assert resp.headers["Location"] == "/api/users/42"
resp = client.post("/theme")
assert "theme=dark" in resp.headers["Set-Cookie"]

Thói quen này giúp phát hiện sớm những response “đúng dữ liệu nhưng sai status, thiếu header” — loại lỗi khó thấy khi chỉ nhìn trên trình duyệt.

Bài viết liên quan

Câu hỏi thường gặp

make_response() khác gì so với trả tuple (body, status, headers)?

Tuple đủ cho status và header đơn giản, nhưng không gắn được cookie. make_response() trả về đối tượng Response đầy đủ, nên bạn dùng được set_cookie(), delete_cookie() và sửa header nhiều lần.

Khi nào không cần make_response()?

Khi view chỉ trả dữ liệu với status 200, hoặc chỉ cần đổi status thì tuple (body, status) đã đủ. Dùng make_response() khi response cần header tùy chỉnh, cookie, hoặc cả ba thứ cùng lúc.

set_cookie() khác gì session của Flask?

set_cookie() ghi một cookie thô mà bạn tự đọc lại bằng request.cookies. Session của Flask cũng lưu trong cookie nhưng được ký bằng SECRET_KEY và tự giải mã thành dict. Đừng lưu dữ liệu nhạy cảm vào cookie thô chưa ký.

Vì sao đã gọi delete_cookie() mà trình duyệt vẫn gửi cookie cũ?

Vì delete_cookie() chỉ gửi cookie hết hạn về trình duyệt; trình duyệt chỉ xóa sau khi nhận response đó. Nếu bạn kiểm tra trước khi response tới nơi, cookie cũ vẫn còn đó.

Ghi đè header đã có sẵn thì sao?

Gán response.headers["X-Request-Id"] = ... sẽ thay thế giá trị cũ. Muốn giữ nhiều giá trị cho cùng một header, dùng response.headers.add() thay vì gán trực tiếp.

💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.

Nguồn tham khảo