When a Flask endpoint has to return large data — for example, a CSV file with several hundred thousand rows — don’t collect everything into one string and only then return it: the server holds all the data in RAM, and the client waits until everything is done before receiving the first byte. The right approach is to stream it in parts with a generator combined with stream_with_context: Flask sends each chunk as soon as it is ready, RAM only holds a small portion at a time, and the request context stays alive throughout the streaming.

The Problem

Suppose you write an endpoint that exports order data as CSV. Many people collect every row into a list and then join them into one large string before returning the response. That is fine for a few thousand rows, but with several hundred thousand rows, the server’s RAM grows to match the size of the file, while the client waits a full minute staring at a blank screen before seeing the first byte — and if the request times out midway, all that work goes down the drain.

The second trap is subtler: you switch to a generator to keep RAM light, but the generator reads data from the database through g or the session. Flask pops the request context as soon as the view function returns, so by the time the generator starts running, g is gone and you get a RuntimeError: Working outside of application context — a familiar error for anyone streaming in Flask for the first time.

from flask import Flask, Response
app = Flask(__name__)
@app.route("/export")
def export():
rows = ["id,total"]
for order in Order.query.all(): # tải hết vào RAM
rows.append(f"{order.id},{order.total}")
return Response("\n".join(rows), mimetype="text/csv")

The Solution

Wrap the generator with stream_with_context and return it through a Response. Flask keeps the request context alive for the whole streaming process, so the generator can still use g, request, and the session just like in a normal view, while sending each chunk to the client as soon as it is produced. Combine it with SQLAlchemy’s yield_per() to read the data in small batches.

from flask import Flask, Response, stream_with_context
app = Flask(__name__)
@app.route("/export")
def export():
def generate():
yield "id,total\n"
for order in Order.query.yield_per(1000): # đọc từng đợt 1000 dòng
yield f"{order.id},{order.total}\n"
return Response(
stream_with_context(generate()),
mimetype="text/csv",
headers={"Content-Disposition": "attachment; filename=orders.csv"},
)

Because Flask does not know the total length in advance, the response uses chunked transfer encoding, and the browser starts downloading the file from the very first chunk. Note: do not set the Content-Length header yourself when streaming, because you do not know the exact length in advance.

This approach is not only for CSV. You can stream JSON Lines with mimetype="application/x-ndjson" so the client processes each line as soon as it arrives, or stream logs in real time for a progress-tracking page.

Apply It to Your Project

Review your endpoints that export files, generate reports, or return long lists: wherever you are collecting all results into a list before returning, switch to a generator wrapped in stream_with_context. Start with the heaviest endpoint — usually a CSV export or a report — and measure peak RAM before and after the change. Remember the golden rule: any generator that touches g, request, or the database must always go through stream_with_context, or the RuntimeError will be waiting for you in production.

Related Posts

Frequently Asked Questions

What happens if I don’t use stream_with_context?

The generator still runs, but the request context has already been popped after the view returns. The moment the generator touches g, request, session, or the database, you get a RuntimeError right away — Working outside of application context for g/session, and Working outside of request context for request. A generator that only yields fixed strings will work without the wrapper, but wrapping it anyway costs nothing.

Does streaming make the response slower?

The total time is almost unchanged, but the client receives the first byte much sooner, because Flask sends each chunk as soon as it is ready instead of waiting until everything is collected. For large files, the user experience is noticeably better and the risk of a mid-stream timeout drops too.

Can I use stream_with_context for JSON?

Yes. Yield one JSON object per line in JSON Lines format, set mimetype="application/x-ndjson", and the client processes each line as soon as it arrives. Don’t stream one large JSON object by yielding it piece by piece, because the client has to wait for all the pieces before it can parse anything — that throws away the whole benefit of streaming.

💬 Have you tried this in your own project? If you run into an error or have a neater way to do it, leave a comment below — I read them all and will add the best ideas to the post.

References