Skip to main content
PhotoFlow uses Socket.io to provide real-time synchronization of data changes across all connected clients. When any client modifies data, all other clients are immediately notified.

How It Works

The real-time system uses a simple event-based architecture:
  1. A client makes a change (creates/updates/deletes a task or comment)
  2. The client emits a database-change event to the server
  3. The server broadcasts a database-changed event to all connected clients
  4. Each client receives the event and refreshes its data

Server Configuration

The Socket.io server is integrated into the Vite development server via a custom plugin in vite.config.js:
import { Server } from 'socket.io';

const io = new Server(server.httpServer, {
  cors: {
    origin: process.env.VITE_CORS_ORIGIN || '*',
    methods: ['GET', 'POST']
  }
});

io.on('connection', (socket) => {
  socket.on('database-change', (msg) => {
    io.emit('database-changed', msg);
  });
});

Environment Variables

VITE_SOCKET_URL
string
The Socket.io server URL. Defaults to http://localhost:3000 if not set.
VITE_CORS_ORIGIN
string
CORS origin for Socket.io connections. Defaults to * (allow all) if not set.

Client Setup

To connect to the PhotoFlow Socket.io server from your client application:

Installation

npm install socket.io-client

Basic Connection

import { io } from 'socket.io-client';

const ENDPOINT = process.env.VITE_SOCKET_URL || 'http://localhost:3000';
const socket = io(ENDPOINT);

export { socket };

Events

Emitting Changes

When you make a change to the database via the API, emit a database-change event:
import { socket } from './socket';

// After creating/updating/deleting a task
async function createTask(taskData) {
  const response = await fetch('/api/createNewTask', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(taskData)
  });
  
  if (response.ok) {
    // Notify other clients
    socket.emit('database-change', { type: 'task-created' });
  }
}
database-change
event
Event to emit when you make a database change. The message payload is flexible and can contain any data you want to pass to other clients.

Listening for Changes

Listen for the database-changed event to be notified when other clients make changes:
import { socket } from './socket';

socket.on('database-changed', (msg) => {
  console.log('Database changed:', msg);
  
  // Refresh your local data
  fetchTasks();
});
database-changed
event
Event emitted by the server when any client triggers a database change. Contains the same payload that was sent in the database-change event.

Complete Example

Here’s a complete example of integrating Socket.io with task management:
import { io } from 'socket.io-client';

// Initialize socket connection
const socket = io('http://localhost:3000');

// Listen for changes
socket.on('database-changed', (msg) => {
  console.log('Received update:', msg);
  refreshTasks();
});

// Create a new task and notify others
async function createTask(taskData) {
  try {
    const response = await fetch('/api/createNewTask', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(taskData)
    });
    
    const result = await response.json();
    
    if (result === 'Successful!') {
      // Notify all clients about the change
      socket.emit('database-change', {
        type: 'task-created',
        timestamp: new Date().toISOString()
      });
    }
  } catch (error) {
    console.error('Failed to create task:', error);
  }
}

// Refresh local task list
async function refreshTasks() {
  const response = await fetch('/api/getUserTasks', {
    method: 'POST'
  });
  const tasks = await response.json();
  // Update UI with new tasks
  updateTaskList(tasks);
}

Connection Events

Socket.io provides built-in connection events:
socket.on('connect', () => {
  console.log('Connected to PhotoFlow server');
});

socket.on('disconnect', () => {
  console.log('Disconnected from PhotoFlow server');
});

socket.on('connect_error', (error) => {
  console.error('Connection error:', error);
});

Best Practices

Debounce refreshes: When receiving database-changed events, consider debouncing your data refresh function to avoid excessive API calls if multiple changes occur rapidly.
Include context in events: Pass meaningful data in your event payloads (like type, taskId, timestamp) to help clients decide whether they need to refresh.
No authentication: The Socket.io server does not implement authentication. Anyone who can connect to the socket can emit events and trigger refreshes in all clients.

Troubleshooting

Connection Fails

If the socket connection fails:
  1. Verify VITE_SOCKET_URL is set correctly
  2. Check that the Vite dev server is running
  3. Verify CORS settings if connecting from a different origin

Events Not Received

If clients don’t receive updates:
  1. Ensure you’re calling socket.emit('database-change') after API calls
  2. Check the browser console for socket connection errors
  3. Verify the event name is exactly database-change (not database-changed)

Build docs developers (and LLMs) love