# Simulated data source (e.g., could be database records or LLM outputs)
def generate_data_records():
    """A generator function that simulates fetching data records one by one."""
    records = [
        {"id": 1, "event": "user_signup", "timestamp": "2025-05-21T10:15:00Z", "details": {"user_id": "u123", "method": "email"}},
        {"id": 2, "event": "item_purchase", "timestamp": "2025-05-21T10:15:30Z", "details": {"item_id": "p456", "quantity": 2}},
        {"id": 3, "event": "image_processed", "timestamp": "2025-05-21T10:16:00Z", "details": {"image_id": "i789", "status": "completed"}},
        # Simulate a stream that continues for a bit
        {"id": 4, "event": "llm_token", "timestamp": "2025-05-21T10:16:05Z", "details": {"token": "Hello"}},
        {"id": 5, "event": "llm_token", "timestamp": "2025-05-21T10:16:06Z", "details": {"token": " world"}},
        {"id": 6, "event": "llm_token", "timestamp": "2025-05-21T10:16:07Z", "details": {"token": "!"}},
    ]
    for record in records:
        yield record
        # Crucial: Simulate work/delay for each record
        # This makes the TTFB difference apparent in benchmarks
        time.sleep(0.5)

@app.route('/stream-data') # Our existing JSONL streaming endpoint
def stream_data():
    def generate_jsonl():
        for record in generate_data_records():
            json_string = json.dumps(record)
            yield json_string + '\n'
    return Response(generate_jsonl(), mimetype='application/jsonl')

@app.route('/traditional-data') # New endpoint for traditional JSON
def traditional_data():
    all_records = []
    for record in generate_data_records():
        all_records.append(record)
    # Sends the entire list as a single JSON array response
    return jsonify(all_records)

if __name__ == '__main__':
    app.run(debug=True, threaded=True) # threaded=True can help with concurrent requests during testing
```

Ensure this updated `app.py` is running. If it was already running from the previous section, it will most likely auto-reload, but you might need to stop and restart it to activate the new `/traditional-data` route.

### Running the benchmark

We'll use `curl` along with its `-w` (write-out) option to capture specific timing information, especially `time_starttransfer`, which is our TTFB. The `time` utility (often a shell built-in command) will measure the total wall-clock time for the `curl` command.

Open your terminal (while `app.py` is running in another) and execute the following commands to measure the TTFB and total time.

For JSONL streaming:

```bash
time curl -N -s -o /dev/null -w "TTFB (JSONL Stream): %{time_starttransfer} s\nTotal time (curl JSONL): %{time_total} s\n" http://127.0.0.1:5000/stream-data
```

For traditional JSON:

```bash
time curl -s -o /dev/null -w "TTFB (Traditional JSON): %{time_starttransfer} s\nTotal time (curl Traditional): %{time_total} s\n" http://127.0.0.1:5000/traditional-data
```

In these code blocks:

- The `-N` or `--no-buffer` option for `curl` disables buffering of the output stream, allowing us to see streaming behavior.
- The `-s` option silences progress output from `curl`.
- The `-o /dev/null` option discards the actual response body so it doesn't clutter the terminal (we're interested in timings here).
- The `-w "..."` option formats the output from `curl` after the transfer:

  - `%{time_starttransfer}` is the TTFB.
  - `%{time_total}` is the total transaction time measured by `curl` itself.

- The `time` command prefixing `curl` gives an overall execution time from the shell's perspective.

### Results

In our tests, we saw the following results:

|                  | TTFB       | Total time |
| ---------------- | ---------- | ---------- |
| curl JSONL       | 0.001550 s | 3.021876 s |
| curl traditional | 3.018902 s | 3.019132 s |

## What about subjective observations?

We've seen the numbers, but we know that the perception of something as "fast" or "slow" is subjective. So let's look at the qualitative differences in how the data arrives.

### Observing the stream

To visually see the difference in how the data arrives, run `curl` without discarding the output.

- For JSONL streaming:

  ```bash
  curl -N http://127.0.0.1:5000/stream-data
  ```

  You'll see the data appear line by line. Each JSON line prints about half a second after the previous line.

- For traditional JSON:

  ```bash
  curl http://127.0.0.1:5000/traditional-data
  ```

  You'll see there is a pause of about three seconds, then the entire JSON array is printed at once.

## Interpreting the benchmark

The results should clearly demonstrate:

- **Vastly superior TTFB for JSONL:** This is the most critical takeaway. JSONL allows the server to send initial data almost immediately after it's ready, significantly improving perceived performance and enabling real-time updates for the client. In our simulation, this is because the first `time.sleep(0.5)` elapses, one record is yielded, and Flask sends it.
- **Client-side processing can begin sooner with JSONL:** Because each line is a complete JSON object, the client can start parsing and using the data as it arrives. With traditional JSON, the client must wait for the entire payload.
- **JSONL improves server-side memory efficiency (implied):** While this benchmark doesn't directly measure server memory, recall that the `/stream-data` endpoint processes one record at a time, while `/traditional-data` builds a list of `all_records` in memory. For large datasets, this difference is important for server stability and scalability. Our example dataset is small, but extrapolate this to millions of records, and the traditional approach becomes infeasible or very resource-intensive.

The `time.sleep(0.5)` in `generate_data_records` is key to this demonstration. It simulates the real-world scenario where records aren't all available instantaneously (for example, they may be results from an LLM generating tokens, database queries, or other microservice calls). Without such a delay in a local benchmark against a very fast data generator, the TTFB for both might appear small and hide the architectural benefit.

This benchmark should provide compelling evidence that when TTFB matters, JSONL is a significantly better method than waiting for a complete JSON dataset.

## How Speakeasy enables API producers to stream responses

Speakeasy SDKs include built-in JSONL streaming support. To enable this for your API:

1. Set the response content type to `application/jsonl` in your OpenAPI spec
2. Speakeasy generates SDK code that handles the streaming automatically
3. Your API/SDK users get clean, easy-to-use streaming methods

Here's an example of how to set the `application/jsonl` content type in your OpenAPI document:

```yaml
paths:
  /logs:
    get:
      summary: Stream log events
      operationId: stream
      tags:
        - logs
      parameters:
        - name: query
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Log events stream
          content:
            # Either content type can be used:
            application/jsonl:
              schema:
                $ref: "#/components/schemas/LogEvent"
            # OR
            application/x-ndjson:
              schema:
                $ref: "#/components/schemas/LogEvent"
components:
  schemas:
    LogEvent:
      description: A log event in line-delimited JSON format
      type: object
      properties:
        timestamp:
          type: string
        message:
          type: string
```

See our documentation on [enabling JSON lines responses](https://www.speakeasy.com/docs/customize/runtime/jsonl-events) for detailed information about how best to prepare your OpenAPI document for streaming JSONL responses.

Speakeasy also generates the necessary code for SSE, so your users don't need to worry about the underlying implementation details. They can focus on building their applications and consuming your API.

From your user's perspective, SDK implementations of your API will look like this:

```python filename="example.py"
from speakeasy_sdk import SDK

with SDK() as sdk:
    stream = sdk.analytics.stream(
        start_date="2024-01-01"
    )

    with stream.object as jsonl_stream:
        for event in jsonl_stream:
            # Handle the event
            print(event, flush=True)
```

We hope this guide has shown you that streaming JSONL responses is not only possible, but simple and efficient. By using JSONL, you can provide a better user experience, reduce server load, and make your API more responsive.