Falcon::RailsGuidesServer-Sent Events

Server-Sent Events

This guide explains how to implement Server-Sent Events with Falcon and Rails.

What are Server-Sent Events?

Server-Sent Events (SSE) provide a way to push real-time updates from the server to the client over a single HTTP connection. Unlike WebSockets, SSE is unidirectional (server-to-client only) and uses the standard HTTP protocol.

When to use SSE:

When NOT to use SSE:

Basic Implementation

Server-Side: Rails Controller

Create a controller action that streams events using Rack::Response:

class SseController < ApplicationController
	def index
		# Render the page with EventSource JavaScript
	end
	
	EVENT_STREAM_HEADERS = {
		'content-type' => 'text/event-stream',
		'cache-control' => 'no-cache',
		'connection' => 'keep-alive'
	}

	def events
		body = proc do |stream|
			while true
				# Send timestamped data:
				stream.write("data: #{Time.now}\n\n")
				sleep 1
			end
		end
		
		self.response = Rack::Response[200, EVENT_STREAM_HEADERS.dup, body]
	end
end

Key Points:

Client-Side: JavaScript EventSource

Create an HTML page that consumes the SSE stream:

<div id="status" class="status warning">🔄 Connecting to event stream...</div>
<div id="history" class="terminal"></div>

<script>
var eventSource = new EventSource("events");
var messageCount = 0;

eventSource.addEventListener("open", function(event) {
	document.getElementById("status").innerHTML = "✅ Connected to event stream";
	document.getElementById("status").className = "status success";
});

eventSource.addEventListener("message", function(event) {
	messageCount++;
	var container = document.createElement("div");
	container.className = "terminal-line";
	container.innerHTML = `<span class="event-count">#${messageCount}</span> <span class="event-data">${event.data}</span>`;
	
	var history = document.querySelector("#history");
	history.appendChild(container);
	history.scrollTop = history.scrollHeight;
	
	// Keep only last 20 messages to prevent memory issues
	if (history.children.length > 20) {
		history.removeChild(history.firstChild);
	}
});

eventSource.addEventListener("error", function(event) {
	document.getElementById("status").innerHTML = "❌ Connection error - attempting to reconnect...";
	document.getElementById("status").className = "status error";
});
</script>

Key Points:

Routing Configuration

Add routes to your config/routes.rb:

Rails.application.routes.draw do
	# SSE Example:
	get 'sse/index'    # Page with EventSource JavaScript
	get 'sse/events'   # SSE endpoint
end

Advanced Patterns

Sending Custom Event Types

By default, event: message is assumed for each record sent on the stream. However, it's possible to specify different kinds of events:

def events
	body = proc do |stream|
		# Send different event types
		stream.write("event: user_joined\n")
		stream.write("data: #{user.to_json}\n\n")
		
		stream.write("event: message\n")
		stream.write("data: #{message.to_json}\n\n")
	end
	
	self.response = Rack::Response[200, EVENT_STREAM_HEADERS.dup, body]
end

On the client, you need to register multiple event listeners:

eventSource.addEventListener("user_joined", function(event) {
	var user = JSON.parse(event.data);
	// Handle user joined event
});

eventSource.addEventListener("message", function(event) {
	var message = JSON.parse(event.data);
	// Handle message event
});