Frontend Development 13 min read

Why Server‑Sent Events (SSE) Power Real‑Time Typing Effects in AI Chat Apps

Server‑Sent Events (SSE) offer a lightweight, HTTP‑native solution for real‑time, one‑way streaming, making them ideal for AI chat applications that need a typewriter‑style response, with advantages over WebSockets such as easy implementation, automatic reconnection, broad compatibility, and simple debugging.

Architecture and Beyond
Architecture and Beyond
Architecture and Beyond
Why Server‑Sent Events (SSE) Power Real‑Time Typing Effects in AI Chat Apps

SSE Advantages

In AI chat interfaces, a "typewriter" effect shows the model's answer token by token. Both DeepSeek and Tencent Yuanbao adopt Server‑Sent Events (SSE) for this real‑time streaming.

Why Choose SSE?

The benefits are fourfold:

Perfect scenario match : AI dialogue follows a simple request‑response flow—user sends a prompt, the server streams a one‑way response. SSE’s unidirectional (server → client) model fits this exactly, unlike WebSocket’s full‑duplex channel which adds unnecessary complexity.

Native HTTP support : SSE is built on standard HTTP. Establishing a connection is just a normal GET request with Content-Type: text/event-stream , avoiding the protocol‑upgrade handshake required by WebSocket ( ws:// / wss:// ).

Automatic reconnection and fault tolerance : Browsers automatically retry broken connections using the retry field and resend the last id via the Last-Event-ID header, enabling seamless resume without custom JavaScript.

Easy debugging : Because the stream is plain text over HTTP, developers can view it directly in the browser address bar, curl , or Chrome DevTools network panel, whereas WebSocket frames require specialized tools.

Implementing SSE for a Typing Effect

Backend (Python + Flask)

<code>from flask import Flask, Response, request
import openai, json

app = Flask(__name__)

@app.route('/chat-stream')
def chat_stream():
    prompt = request.args.get('prompt')
    def generate_events():
        try:
            response_stream = openai.ChatCompletion.create(
                model="gpt-4",
                messages=[{"role": "user", "content": prompt}],
                stream=True
            )
            for chunk in response_stream:
                content = chunk.choices[0].delta.get('content', '')
                if content:
                    sse_data = f"data: {json.dumps({'token': content})}\n\n"
                    yield sse_data
            yield "event: done\ndata: [STREAM_END]\n\n"
        except Exception as e:
            error_message = f"event: error\ndata: {json.dumps({'error': str(e)})}\n\n"
            yield error_message
    return Response(generate_events(), mimetype='text/event-stream')

if __name__ == '__main__':
    app.run(threaded=True)
</code>

Key points:

stream=True requests a streaming response from the LLM.

The generator generate_events yields each token wrapped in SSE format ( data: ...\n\n ).

Flask returns a Response with MIME type text/event-stream .

Frontend (JavaScript)

<code><!-- HTML structure -->
<div id="chat-box"></div>
<input id="user-input" type="text">
<button onclick="sendMessage()">Send</button>

<script>
let eventSource;
function sendMessage() {
    const input = document.getElementById('user-input');
    const prompt = input.value;
    input.value = '';
    const chatBox = document.getElementById('chat-box');
    const aiMessageElement = document.createElement('p');
    aiMessageElement.textContent = 'AI: ';
    chatBox.appendChild(aiMessageElement);
    eventSource = new EventSource(`/chat-stream?prompt=${encodeURIComponent(prompt)}`);
    eventSource.onmessage = function(event) {
        const data = JSON.parse(event.data);
        const token = data.token;
        if (token) {
            aiMessageElement.textContent += token;
        }
    };
    eventSource.addEventListener('done', function(event) {
        console.log('Stream finished:', event.data);
        eventSource.close();
    });
    eventSource.onerror = function(err) {
        console.error('EventSource failed:', err);
        aiMessageElement.textContent += ' [Error, connection lost]';
        eventSource.close();
    };
}
</script>
</code>

The front‑end creates an EventSource to the SSE endpoint, appends each received token to the same paragraph, and closes the connection when a done event or error occurs.

History and Specification of SSE

Before SSE, developers used techniques such as short polling, long polling, and the umbrella term "Comet" to simulate server push. SSE originated from the WHATWG Web Applications 1.0 proposal in 2004, was first implemented experimentally by Opera in 2006, and later standardized in HTML5 with the MIME type text/event-stream .

SSE messages are UTF‑8 encoded text streams with four standard fields:

Event – the event type.

Data – the payload.

ID – a unique identifier for each event.

Retry – the reconnection delay in milliseconds.

In practice, implementations may deviate from the spec, especially regarding custom reconnection logic.

Browser Compatibility

Modern browsers fully support SSE, as shown in the compatibility chart below.

References

https://en.wikipedia.org/wiki/Server-sent_events

https://learn.microsoft.com/zh-cn/azure/application-gateway/for-containers/server-sent-events?tabs=server-sent-events-gateway-api

https://www.cnblogs.com/openmind-ink/p/18706352

https://javascript.ruanyifeng.com/htmlapi/eventsource.html

JavaScriptPythonReal-time StreamingWeb DevelopmentServer-Sent EventsSSE
Architecture and Beyond
Written by

Architecture and Beyond

Focused on AIGC SaaS technical architecture and tech team management, sharing insights on architecture, development efficiency, team leadership, startup technology choices, large‑scale website design, and high‑performance, highly‑available, scalable solutions.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.