Skip to content
Back to Blog
July 31, 2026

Streaming vs Bidirectional Push: Why Travel Apps Often Need Both

A practical guide to choosing the right real-time protocol for different features in a travel booking platform — server streaming for AI, bidirectional sockets for presence.

Streaming vs Bidirectional Push: Why Travel Apps Often Need Both
M

The internet loves a good "X vs. Y" debate, and the bidirectional sockets vs. Server streaming argument is a reliable one. You'll find strong opinions on both sides. What you won't find as often is someone saying "we use both, and here's specifically when we use each one."

That's our position. We use unidirectional streaming for AI responses and bidirectional sockets for presence and collaborative features. Not because we couldn't pick one, but because they solve different problems and forcing one protocol to do everything creates unnecessary complexity.

Two problems, two protocols

Illustration for this section

The first problem is AI response delivery. When a traveler sends a message, the AI agent processes it and generates a response. This is fundamentally unidirectional. The server produces data, the client consumes it. The client has no reason to send data back to the server during the stream. It just needs to receive tokens, tool results, and status updates as they generate.

The second problem is real-time presence. Is the traveler online? Are they typing? Did they read the last message? Are multiple users looking at the same trip? This is fundamentally bidirectional. Both sides need to send and receive data at arbitrary times, with low latency, without the overhead of establishing a new HTTP connection for each message.

These are different communication patterns, and optimizing for one compromises the other.

Server streaming for AI streaming: the case

Server streaming runs over standard HTTP. The client makes a GET request. The server holds it open and pushes data as `text/event-stream`. When the response is done, the connection closes.

For AI response streaming, this simplicity is a massive advantage.

Proxy compatibility. server streaming works through every reverse proxy, CDN, and load balancer we've encountered without special configuration. The request looks like a normal HTTP request that just takes a long time to respond. Nginx, Cloudflare, AWS ALB, they all handle it natively.

No sticky sessions. Since each streaming connection is a standalone HTTP request, load balancers can distribute them normally. There's no connection state that needs to route to a specific server instance.

Native browser support. The `EventSource` API is built into every browser. On the web client, we use the `fetch` API with `ReadableStream` for more control, but the basic protocol support is universal.

Automatic reconnection. The `EventSource` API includes built-in reconnection with `Last-Event-ID` headers. We handle reconnection ourselves for more control, but having a reasonable default in the protocol is nice.

The main limitation of server streaming is that it's unidirectional. The client can't send data back through the streaming connection. For AI streaming, that's not a limitation. It's the correct design. If the client needs to send a new message, it opens a new HTTP request. The AI response comes back over a new server streaming stream.

Bidirectional sockets for presence: the case

Supporting diagram

Bidirectional sockets are a different animal. They start as an HTTP request, then upgrade to a persistent, bidirectional TCP connection. Both sides can send messages at any time with minimal overhead per message.

For presence features, this is necessary.

Typing indicators need to fire in real time, potentially several times per second, with the minimal latency of an already-open connection. Opening a new HTTP request for each "user is typing" event would be absurd.

Online/offline status needs to be tracked continuously. A bidirectional socket connection's lifecycle naturally maps to presence. Connected means online. Disconnected means offline. No polling required.

Collaborative features where multiple users interact with the same trip or itinerary need a shared real-time channel. bidirectional sockets give each participant a persistent connection to push and receive updates.

The operational cost is real, though. bidirectional sockets require connection-aware load balancing. If you're running multiple server instances, you need a way to route messages to the right connections, typically through a pub/sub layer like an in-memory data store. You also need to handle connection lifecycle more carefully: heartbeats, idle timeouts, reconnection, and cleanup of stale connections.

Why not just use bidirectional sockets for everything

I've heard this argument many times. "bidirectional sockets can do everything server streaming does plus more, so just use bidirectional sockets." Technically true. Practically wrong.

Using bidirectional sockets for AI streaming means you need sticky sessions or a message routing layer for every AI response. That's a bunch of infrastructure for a feature that doesn't benefit from bidirectional communication. The AI doesn't need to receive messages from the client mid-stream.

It also means you're maintaining persistent connections for something that's inherently request-response shaped. The traveler sends a message, gets a response, then maybe sends another message minutes later. A bidirectional socket connection sitting idle between messages is wasting server resources and client battery.

streaming connections open for the duration of a response and close when it's done. Clean. No idle connections to manage. No connection pool to size.

Why not just use server streaming for everything

The reverse argument has the same problem. Server streaming is unidirectional. Building bidirectional features on top of it means the client has to use separate HTTP requests for the "send" direction, and you lose the low-latency, always-open channel that makes typing indicators and presence feel instant.

You'd end up polling for presence, which adds latency and server load. Or you'd build a second server streaming stream from client to server, which is just bidirectional sockets with more steps and worse ergonomics.

Proxy and load balancer considerations

In production, the protocol choice has implications beyond the application layer.

Server streaming requests look like long-lived HTTP responses. Some proxies have timeout settings that will kill long-running responses. We configure our proxies with extended timeout windows for server streaming endpoints. But the key point is that we're configuring existing timeout settings, not adding protocol-specific support.

a bidirectional socket requests require an HTTP upgrade, and some proxies need explicit configuration to support this. Load balancers need to understand that a bidirectional socket connection is stateful and should route traffic to the same backend instance for the duration of the connection, or you need a pub/sub layer that makes this routing transparent.

We maintain separate routing rules for server streaming and a bidirectional socket traffic. Server streaming endpoints get standard HTTP load balancing with extended timeouts. a bidirectional socket endpoints get connection-aware routing with pub/sub for cross-instance message delivery.

Connection lifecycle management

Both protocols need lifecycle management, but the patterns differ.

streaming connections are simple. They open, deliver events, and close. If a connection hangs (the client disconnects without the server knowing), the server detects it on the next write attempt and cleans up. The lifecycle is tied to the request-response cycle.

a bidirectional socket connections are long-lived. They need explicit keepalive mechanisms (ping/pong frames) to detect dead connections. They need idle timeout policies to reclaim resources from connections that are technically alive but not doing anything useful. They need graceful shutdown procedures for server deployments so existing connections aren't dropped abruptly.

We track connection counts for both protocols, but a bidirectional socket connections get more attention because their long-lived nature makes them a bigger scaling factor. streaming connections are self-limiting: they exist only during active AI responses.

When polling is actually fine

Not every real-time feature needs a real-time protocol. Some updates are needed infrequently enough that polling is the right answer.

Trip status updates (new document added, booking status changed) happen at most a few times per day. Polling every 30 seconds is plenty. Opening a bidirectional socket connection or server streaming stream for something that updates twice a day is overengineering.

We poll for trip list updates, document status changes, and notification badge counts. The update frequency doesn't justify a persistent connection, and the slight delay from polling is imperceptible.

Decision matrix

When we add a new real-time feature, we ask three questions:

Is it server-to-client only? If yes, server streaming is the default choice. AI responses, progress updates for long-running operations, and system announcements are all server-to-client.

Does it require low-latency bidirectional communication? If yes, bidirectional sockets. Typing indicators, cursor presence, and collaborative editing need both directions with minimal delay.

Does it update less than once per 30 seconds? If yes, polling. Don't waste a persistent connection on infrequent updates.

The answer is almost never "we need something more exotic." server streaming, bidirectional sockets, and polling cover every real-time requirement we've encountered. The trick is using each one where it fits instead of trying to make one protocol do everything.


Nowah is an AI travel agent that searches and books real flights and hotels through conversation — no filters, no thirty open tabs. Plan your next trip.

Share this article

Ready to Plan with Nowah?

Bring the idea. Nowah will help turn it into a trip.

Try Nowah