Sarıkaya Dev Logo

Implement Real-Time Server-Sent Events in .NET 8 Minimal APIs with Redis Backplane

Mahmut Sarıkaya 4 min read 6 Views 0
Implement Real-Time Server-Sent Events in .NET 8 Minimal APIs with Redis Backplane

Why real‑time updates matter for modern web apps

Imagine a stock‑trading dashboard that must push price changes the instant they occur. A delay of even a few seconds can turn a profit into a loss. Server‑Sent Events (SSE) give browsers a lightweight, one‑way push channel that works over standard HTTP, making it ideal for live feeds, notifications, or collaborative dashboards.

Prerequisites and system requirements

Before writing code, ensure you have .NET 8 SDK (released November 2023) and a running Redis instance (version 6.2 or later). Docker makes a quick Redis spin‑up:

docker run -d --name redis -p 6379:6379 redis:6.2-alpine

Creating a Minimal API project

Open a terminal and run the following commands. The template generates a lean Program.cs file that we will extend.

dotnet new web -n RealTimeSseDemo --framework net8.0
cd RealTimeSseDemo

Next, add the Redis client package.

dotnet add package StackExchange.Redis

Defining the SSE endpoint

Minimal APIs let you declare routes directly in Program.cs. The following snippet registers "/events" as an SSE stream. It writes a comment line to keep the connection alive and then flushes each message as JSON.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<ConnectionMultiplexer>(sp =>
ConnectionMultiplexer.Connect("localhost:6379"));
var app = builder.Build();
app.MapGet("/events", async (HttpContext ctx, ConnectionMultiplexer redis) =>
{
ctx.Response.Headers.Add("Content-Type", "text/event-stream");
ctx.Response.Headers.Add("Cache-Control", "no-cache");
var subscriber = redis.GetSubscriber();
var channel = "sse:notifications";
// Keep‑alive comment every 15 seconds
var timer = new System.Timers.Timer(15000);
timer.Elapsed += (s, e) => ctx.Response.WriteAsync(": keep-alive\n\n");
timer.Start();
await foreach (var message in subscriber.SubscribeAsync(channel))
{
var payload = $"data: {message.Message}\n\n";
await ctx.Response.WriteAsync(payload);
await ctx.Response.Body.FlushAsync();
}
});
app.Run();

Notice the use of ConnectionMultiplexer from StackExchange.Redis to subscribe to a channel that acts as the backplane. Every published message is forwarded to all connected browsers.

Publishing events from any part of the app

Because the Redis connection is registered as a singleton, you can inject it wherever you need to push updates—controllers, background services, or even a CLI tool.

public class PricePublisher
{
private readonly IConnectionMultiplexer _redis;
public PricePublisher(IConnectionMultiplexer redis) => _redis = redis;
public async Task PublishAsync(string symbol, decimal price)
{
var channel = "sse:notifications";
var payload = $"{{\"symbol\":\"{symbol}\",\"price\":{price}}}";
await _redis.GetSubscriber().PublishAsync(channel, payload);
}
}

Inject PricePublisher into a background worker that polls an external market API every 5 seconds, then call PublishAsync. All browsers listening on "/events" will receive the JSON instantly.

Client‑side consumption

The browser code is straightforward. Create an EventSource pointing at the endpoint and handle the message event.

const source = new EventSource('/events');
source.onmessage = e => {
const data = JSON.parse(e.data);
console.log('Update:', data);
// Update the UI, e.g., insert a row in a table
};
source.onerror = err => { console.error('SSE error', err); };

This code works without any additional libraries and automatically reconnects if the connection drops.

Performance tips and scaling considerations

When you expect thousands of concurrent clients, keep these points in mind:

  • Connection limits: .NET 8’s Kestrel defaults to 10 000 concurrent connections. Increase MaxConcurrentConnections in appsettings.json if you exceed that.
  • Redis clustering: Deploy Redis in a clustered mode (3 master nodes) to avoid a single point of failure and to balance publish traffic.
  • Message size: SSE payloads should stay under 1 KB for low latency. Compress large JSON objects before publishing, then decompress client‑side.
  • Back‑pressure: If a client falls behind, the server will buffer messages. Consider adding a simple queue length check and drop older messages when the buffer exceeds 100 items.

Testing the pipeline

Run the API with dotnet run, open the browser console, and then trigger a publish from a separate terminal:

dotnet script -e "using StackExchange.Redis; var r = ConnectionMultiplexer.Connect(\"localhost:6379\"); r.GetSubscriber().Publish(\"sse:notifications\", \"{\"symbol\":\"MSFT\",\"price\":312.45}\");"

If everything is wired correctly, you will see the JSON appear in the console within milliseconds.

Conclusion

Combining .NET 8 Minimal APIs with Server‑Sent Events and a Redis backplane gives you a low‑overhead, horizontally scalable real‑time solution. The code base stays under 50 lines, yet you gain the ability to push updates to any number of browsers without WebSockets or third‑party services. By following the performance checklist, you can safely serve thousands of live feeds in production.

Author: Mahmut Sarıkaya — sarikayadev.com

Sources

Microsoft Docs – ASP.NET Core Minimal APIs
Redis Documentation – Pub/Sub guide
MDN Web Docs – Server‑Sent Events

Tags: #.NET 8 #Minimal APIs #Server‑Sent Events #SSE #Redis backplane
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

1 + 3 =