Skip to main content
The Crypto Shop Backend uses Socket.io to provide real-time notifications when blockchain transactions are confirmed. This guide covers client-side setup and connection management.

Server Configuration

The Socket.io server is initialized when the backend starts:
~/workspace/source/src/config/socket.js:5-12
export const initializeSocket = (server) => {
  io = new Server(server, {
    cors: {
      origin: ['http://localhost:3000', 'http://localhost:5173', process.env.FRONTEND_URL],
      credentials: true,
      methods: ['GET', 'POST']
    }
  });
  // ...
};

CORS Configuration

The server accepts connections from:
  • http://localhost:3000 (default React dev server)
  • http://localhost:5173 (Vite dev server)
  • process.env.FRONTEND_URL (production frontend URL)
Set FRONTEND_URL in your .env file to allow your production frontend to connect:
FRONTEURL=https://your-frontend-domain.com

Client Installation

Install the Socket.io client in your frontend application:
npm install socket.io-client

Basic Client Setup

React Example

Create a Socket.io service for your React application:
src/services/socket.js
import { io } from 'socket.io-client';

const SOCKET_URL = process.env.REACT_APP_API_URL || 'http://localhost:3000';

class SocketService {
  constructor() {
    this.socket = null;
  }

  connect() {
    this.socket = io(SOCKET_URL, {
      withCredentials: true,
      transports: ['websocket', 'polling']
    });

    this.socket.on('connect', () => {
      console.log('Connected to Socket.io server:', this.socket.id);
    });

    this.socket.on('disconnect', (reason) => {
      console.log('Disconnected from Socket.io server:', reason);
    });

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

    return this.socket;
  }

  disconnect() {
    if (this.socket) {
      this.socket.disconnect();
      this.socket = null;
    }
  }

  joinUserRoom(userId) {
    if (this.socket) {
      this.socket.emit('join-user', userId);
    }
  }

  onTransactionConfirmed(callback) {
    if (this.socket) {
      this.socket.on('transaction:confirmed', callback);
    }
  }

  offTransactionConfirmed(callback) {
    if (this.socket) {
      this.socket.off('transaction:confirmed', callback);
    }
  }
}

export default new SocketService();

Vue.js Example

src/plugins/socket.js
import { io } from 'socket.io-client';

const socket = io(import.meta.env.VITE_API_URL || 'http://localhost:3000', {
  withCredentials: true,
  transports: ['websocket', 'polling']
});

socket.on('connect', () => {
  console.log('Socket connected:', socket.id);
});

export default {
  install(app) {
    app.config.globalProperties.$socket = socket;
  }
};

Vanilla JavaScript

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

const socket = io('http://localhost:3000', {
  withCredentials: true,
  transports: ['websocket', 'polling']
});

socket.on('connect', () => {
  console.log('Connected:', socket.id);
});

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

Joining User Rooms

After authentication, join the user-specific room to receive personalized notifications:
~/workspace/source/src/config/socket.js:17-20
socket.on('join-user', (userId) => {
  socket.join(`user:${userId}`);
  console.log(`[Socket] User ${userId} joined room user:${userId}`);
});

Client Implementation

// After user logs in
const userId = '507f1f77bcf86cd799439011'; // From authentication
socket.emit('join-user', userId);
Users must join their room to receive transaction:confirmed events. The room format is user:{userId}.

Connection Lifecycle

React Hook Example

Create a custom hook for Socket.io integration:
src/hooks/useSocket.js
import { useEffect, useRef } from 'react';
import socketService from '../services/socket';
import { useAuth } from './useAuth'; // Your auth hook

export const useSocket = () => {
  const { user } = useAuth();
  const socketRef = useRef(null);

  useEffect(() => {
    // Connect when component mounts
    socketRef.current = socketService.connect();

    // Join user room if authenticated
    if (user?.id) {
      socketService.joinUserRoom(user.id);
    }

    // Cleanup on unmount
    return () => {
      socketService.disconnect();
    };
  }, [user?.id]);

  return socketRef.current;
};
Usage in components:
src/components/OrderPage.jsx
import { useEffect } from 'react';
import { useSocket } from '../hooks/useSocket';
import socketService from '../services/socket';

function OrderPage() {
  const socket = useSocket();

  useEffect(() => {
    const handleTransactionConfirmed = (data) => {
      console.log('Transaction confirmed!', data);
      // Update UI, show notification, etc.
    };

    socketService.onTransactionConfirmed(handleTransactionConfirmed);

    return () => {
      socketService.offTransactionConfirmed(handleTransactionConfirmed);
    };
  }, []);

  return (
    <div>
      {/* Your order page UI */}
    </div>
  );
}

Connection Options

const socket = io(SOCKET_URL, {
  // Enable credentials for CORS
  withCredentials: true,
  
  // Transport methods (try WebSocket first, fallback to polling)
  transports: ['websocket', 'polling'],
  
  // Auto-reconnect settings
  reconnection: true,
  reconnectionDelay: 1000,
  reconnectionDelayMax: 5000,
  reconnectionAttempts: 5,
  
  // Timeout settings
  timeout: 20000,
  
  // Path (if your server uses a custom path)
  path: '/socket.io/'
});

Development vs Production

const socket = io('http://localhost:3000', {
  withCredentials: true,
  transports: ['websocket', 'polling'],
  reconnection: true
});

Error Handling

Handle connection errors gracefully:
socket.on('connect_error', (error) => {
  console.error('Connection failed:', error.message);
  
  if (error.message === 'xhr poll error') {
    // Polling transport failed
    console.log('Retrying with WebSocket only...');
    socket.io.opts.transports = ['websocket'];
  }
});

socket.on('connect_timeout', () => {
  console.error('Connection timeout');
});

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

Testing Connection

Verify your Socket.io setup:
// Test connection
socket.on('connect', () => {
  console.log('✓ Connected to server');
  console.log('Socket ID:', socket.id);
  console.log('Transport:', socket.io.engine.transport.name);
});

// Test user room join
socket.emit('join-user', 'test-user-id');
console.log('✓ Joined user room');

// Test event listener
socket.on('transaction:confirmed', (data) => {
  console.log('✓ Received transaction:confirmed event');
  console.log('Data:', data);
});

Server-Side Connection Handling

The server handles connections as follows:
~/workspace/source/src/config/socket.js:14-25
io.on('connection', (socket) => {
  console.log(`[Socket] User connected: ${socket.id}`);

  socket.on('join-user', (userId) => {
    socket.join(`user:${userId}`);
    console.log(`[Socket] User ${userId} joined room user:${userId}`);
  });

  socket.on('disconnect', () => {
    console.log(`[Socket] User disconnected: ${socket.id}`);
  });
});

Best Practices

Create a singleton Socket.io instance and reuse it across your app:
// ✓ Good: Single instance
const socket = io(SOCKET_URL);
export default socket;

// ✗ Bad: Multiple instances
function Component() {
  const socket = io(SOCKET_URL); // Creates new connection each render!
}
Always remove event listeners when components unmount:
useEffect(() => {
  const handler = (data) => console.log(data);
  socket.on('transaction:confirmed', handler);
  
  return () => {
    socket.off('transaction:confirmed', handler);
  };
}, []);
Re-join user rooms after reconnection:
socket.on('connect', () => {
  if (userId) {
    socket.emit('join-user', userId);
  }
});
Configure Socket.io URL via environment variables:
.env.local
REACT_APP_API_URL=http://localhost:3000

Next Steps

Socket.io Events

Learn about available events and payloads

Transaction Listener

Understand how transactions are monitored

Build docs developers (and LLMs) love