Developer Center » Languages » Javascript » Code Guides » Neo4j Real-time Updates with Server-Sent Events (SSE)

Neo4j Real-time Updates with Server-Sent Events (SSE)

All of the code shown is available in the neo4j-sse-simple-app Github repository.

This application demonstrates how to stream Neo4j query results to browsers in real-time using Server-Sent Events (SSE). Perfect for live dashboards, activity feeds, or any application needing real-time graph data.

What are Server-Sent Events (SSE)?

SSE is a web standard that lets servers push data to browsers over a simple HTTP connection. Unlike WebSockets, SSE is:

  • One-way (server to client) – perfect for broadcasts
  • Auto-reconnects if the connection drops
  • Built into browsers (no libraries needed)
  • Works over standard HTTP

Setup and Dependencies

Minimal dependencies – just Express and the Neo4j driver.

Package initialization – package.json

{
    "name": "neo4j-sse-simple",
    "version": "1.0.0",
    "type": "module",
    "scripts": {
        "start": "node src/index.js"
    },
    "dependencies": {
        "neo4j-driver": "^5.14.0",
        "express": "^4.18.2"
    }
}Code language: JSON / JSON with Comments (json)

Neo4j Connection

Simple connection to the read-only demo database.

Database connection – src/db.js

class Database {

    constructor() {
        this.driver = neo4j.driver( 'neo4j+s://demo.neo4jlabs.com', neo4j.auth.basic('goodreads', 'goodreads') );
    }

    async query(cypher, params = {}) {
        const session = this.driver.session();
        try {
            const result = await session.run(cypher, params);
            return result.records.map(record => record.toObject());
        } finally {
            await session.close();
        }
    }

    async close() {
        await this.driver.close();
    }
}

export default new Database();Code language: JavaScript (javascript)

Server with SSE Endpoint

The main server that streams book recommendations every few seconds.

Express server with SSE – src/index.js

import express from 'express';
import db from './db.js';

const app = express();
// SSE endpoint - streams book recommendations
app.get('/stream', async (req, res) => {
    console.log('📡 Client connected to SSE stream');
    // Set SSE headers
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
    });

    // Send initial message
    res.write('event: connected\n');
    res.write('data: {"message": "Connected to book recommendation stream"}\n\n');

    // Function to send a random book recommendation
    async function sendRecommendation() {
        try {
            // Query for a random highly-rated book
            const books = await db.query(`
            MATCH (b:Book)<-[:AUTHORED]-(a:Author)
            WHERE b.average_rating > 4.2 
              AND b.ratings_count > 50000
            WITH b, collect(a.name) AS authors, rand() AS r
            ORDER BY r
            LIMIT 1
            RETURN b.title AS title, 
                   b.average_rating AS rating,
                   b.ratings_count AS ratingsCount,
                   authors
        `);

            if (books.length > 0) {
                const book = books[0];

                // Send as SSE event
                res.write('event: recommendation\n');
                res.write(`data: ${JSON.stringify({
                    title: book.title,
                    authors: book.authors.slice(0, 3),
                    rating: book.rating,
                    ratingsCount: book.ratingsCount,
                    timestamp: new Date().toISOString()
                })}\n\n`);
            }
        } catch (error) {
            console.error('Query error:', error.message);
        }
    }

    // Send a recommendation every 3 seconds
    const interval = setInterval(sendRecommendation, 3000);

    // Send first recommendation immediately
    sendRecommendation();

    // Clean up on client disconnect
    req.on('close', () => {
        clearInterval(interval);
        console.log('📴 Client disconnected from SSE stream');
    });
});

// Simple HTML client for testing
app.get('/', (req, res) => {
    res.send(` <!DOCTYPE html> <html> <head> <title>Neo4j SSE Book Stream</title> <style> body { font-family: system-ui, -apple-system, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; } h1 { color: white; text-align: center; } .container { background: white; border-radius: 12px; padding: 30px; box-shadow: 0 20px 40px rgba(0,0,0,0.1); } button { background: #667eea; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 16px; cursor: pointer; margin-bottom: 20px; } button:hover { background: #5a67d8; } button:disabled { background: #cbd5e0; cursor: not-allowed; } .book { padding: 15px; margin: 10px 0; border-left: 4px solid #667eea; background: #f7fafc; border-radius: 4px; animation: slideIn 0.5s ease; } @keyframes slideIn { from { opacity: 0; transform: translateX(-20px); } } .book-title { font-weight: bold; color: #2d3748; font-size: 18px; } .book-meta { color: #718096; font-size: 14px; margin-top: 5px; } #status { text-align: center; padding: 10px; margin-bottom: 20px; border-radius: 6px; } .connected { background: #c6f6d5; color: #22543d; } .disconnected { background: #fed7d7; color: #742a2a; } #books { max-height: 400px; overflow-y: auto; } </style> </head> <body> <h1>📚 Real-time Book Recommendations</h1> <div class="container"> <button id="toggleBtn">Start Streaming</button> <div id="status" class="disconnected">Disconnected</div> <div id="books"></div> </div>
 <script>
            let eventSource = null;
            const toggleBtn = document.getElementById('toggleBtn');
            const statusDiv = document.getElementById('status');
            const booksDiv = document.getElementById('books');
            let bookCount = 0;

            function connect() {
                // Create SSE connection
                eventSource = new EventSource('/stream');
                
                eventSource.addEventListener('connected', (e) => {
                    const data = JSON.parse(e.data);
                    console.log('Connected:', data.message);
                    statusDiv.textContent = 'Connected - Receiving recommendations...';
                    statusDiv.className = 'connected';
                    toggleBtn.textContent = 'Stop Streaming';
                });

                eventSource.addEventListener('recommendation', (e) => {
                    const book = JSON.parse(e.data);
                    displayBook(book);
                });

                eventSource.onerror = (e) => {
                    console.error('SSE error:', e);
                    disconnect();
                };
            }

            function disconnect() {
                if (eventSource) {
                    eventSource.close();
                    eventSource = null;
                }
                statusDiv.textContent = 'Disconnected';
                statusDiv.className = 'disconnected';
                toggleBtn.textContent = 'Start Streaming';
            }

            function displayBook(book) {
                const bookDiv = document.createElement('div');
                bookDiv.className = 'book';
                bookDiv.innerHTML = \`
                    <div class="book-title">\${book.title}</div>
                    <div class="book-meta">
                        ⭐ \${book.rating.toFixed(1)} · 
                        👥 \${book.ratingsCount.toLocaleString()} reviews · 
                        ✍️ \${book.authors.join(', ')}
                    </div>
                \`;
                
                booksDiv.insertBefore(bookDiv, booksDiv.firstChild);
                bookCount++;
                
                // Keep only last 10 books
                while (booksDiv.children.length > 10) {
                    booksDiv.removeChild(booksDiv.lastChild);
                }
            }

            toggleBtn.addEventListener('click', () => {
                if (eventSource) {
                    disconnect();
                } else {
                    connect();
                }
            });
        </script>
    </body>
    </html>
`);
});

const PORT = 3000;
app.listen(PORT, () => {
    console.log('\n🚀 SSE Server Running!');
    console.log(`📺 Open http://localhost:${PORT} in your browser`);
    console.log(`📡 SSE endpoint: http://localhost:${PORT}/stream`);
    console.log(`\n💡 The server will stream a new book recommendation every 3 seconds`);
});Code language: JavaScript (javascript)

How It Works

  1. Client connects to /stream endpoint
  2. Server sends a random, highly-rated book every 3 seconds
  3. Browser receives events and displays them in real-time
  4. Auto-reconnect happens if the connection drops (built into EventSource)

Running the Application

# Install Dependencies
npm install

# Run the Server
npm startCode language: JavaScript (javascript)

Open http://localhost:3000 in your browser and click “Start Streaming” to see real-time book recommendations!

Using SSE from JavaScript

Here’s how simple it is to consume the stream:

// Connect to SSE endpoint 
const eventSource = new EventSource('/stream');

// Listen for recommendations 
eventSource.addEventListener('recommendation', (event) => { const book = JSON.parse(event.data); console.log('New recommendation:', book.title); });

// Connection opens 
eventSource.onopen = () => console.log('Connected!');

// Handle errors (auto-reconnects) 
eventSource.onerror = () => console.log('Connection lost, reconnecting...');

// Close when done
eventSource.close();Code language: JavaScript (javascript)

Testing with Multiple Clients

Open multiple browser tabs to see how SSE can broadcast to many clients simultaneously:

# Terminal 1
npm start

# Open Multiple browser tabs
http://localhost:3000 # Tab 1 
http://localhost:3000 # Tab 2 
http://localhost:3000 # Tab 3

# All tabs receive the same stream of recommendations!Code language: Shell Session (shell)

Use Cases

This pattern is perfect for:

  • Live dashboards showing graph metrics
  • Activity feeds (new books, reviews, ratings)
  • Real-time recommendations
  • Monitoring graph changes
  • Progress updates for long-running graph algorithms

The beauty of SSE is its simplicity – no WebSocket complexity, no polling overhead, just a simple HTTP stream of events!

Resources

Share Article