Lesson 55-Fastify and WebSocket Integration

Using the fastify-websocket Library

WebSocket is a protocol enabling full-duplex communication over a single persistent connection, ideal for real-time data pushing, chat applications, gaming servers, and more. Fastify supports WebSocket functionality through the fastify-websocket plugin.

Installing fastify-websocket

Install the fastify-websocket plugin:

npm install fastify-websocket

Configuring WebSocket

Register the fastify-websocket plugin in your Fastify application:

const fastify = require('fastify')();
const fastifyWebsocket = require('fastify-websocket');

fastify.register(fastifyWebsocket);

fastify.listen({ port: 3000 }, (err, address) => {
    if (err) throw err;
    console.log(`Server listening on ${address}`);
});

Creating WebSocket Routes

Once registered, create WebSocket routes using the websocket: true option:

fastify.get('/ws', { websocket: true }, (connection, request) => {
    connection.on('message', (message) => {
        console.log('received: %s', message.toString());
        connection.send(message);
    });
});

Sending and Receiving Messages

Use connection.send() to send messages to clients, which listen for message events:

// Server sending message
connection.send(JSON.stringify({ text: 'Hello from server!' }));

// Client receiving message
socket.addEventListener('message', function(event) {
    console.log('Message from server ', event.data);
});

Example: Simple Chat Application

A basic chat application using Fastify and WebSocket:

const fastify = require('fastify')();
const fastifyWebsocket = require('fastify-websocket');

fastify.register(fastifyWebsocket);

let clients = [];

fastify.get('/ws', { websocket: true }, (connection, request) => {
    clients.push(connection);

    connection.on('message', (message) => {
        const data = JSON.parse(message);
        console.log('received: %s', data.text);

        clients.forEach(client => {
            client.send(JSON.stringify(data));
        });
    });

    connection.once('close', () => {
        clients = clients.filter(c => c !== connection);
    });
});

fastify.listen({ port: 3000 }, (err, address) => {
    if (err) throw err;
    console.log(`Server listening on ${address}`);
});

This broadcasts messages to all connected clients, implementing a simple chat room.

Client Code

Use the browser’s WebSocket object to connect:

<script>
    var socket = new WebSocket('ws://localhost:3000/ws');

    socket.addEventListener('open', function (event) {
        console.log('Connected to server');
    });

    socket.addEventListener('message', function (event) {
        console.log('Message from server ', event.data);
    });

    socket.send(JSON.stringify({ text: 'Hello from client!' }));
</script>

Namespaces and Rooms

Organize connections into rooms or namespaces for applications like chat rooms:

fastify.get('/ws/:room', { websocket: true }, (connection, request) => {
    const room = request.params.room;

    joinRoom(connection, room);

    connection.on('message', (message) => {
        broadcastToRoom(room, message);
    });

    connection.once('close', () => {
        leaveRoom(connection, room);
    });
});

joinRoom, broadcastToRoom, and leaveRoom are custom functions for room management.

Heartbeat Detection

Detect inactive connections with a heartbeat mechanism:

const HEARTBEAT_INTERVAL = 30000;
const TIMEOUT = 10000;

function setupHeartbeat(connection) {
    let timeoutId;

    const sendHeartbeat = () => {
        connection.send('ping');
        timeoutId = setTimeout(() => {
            connection.close();
        }, TIMEOUT);
    };

    connection.on('message', (message) => {
        if (message.toString() === 'pong') {
            clearTimeout(timeoutId);
            timeoutId = null;
            sendHeartbeat();
        }
    });

    sendHeartbeat();
}

Error Handling

Handle WebSocket errors for stability:

connection.on('error', (error) => {
    console.error('WebSocket error:', error);
});

Performance Optimization

For high-concurrency scenarios:

  • Buffers: Use Buffer for efficient large data transfers.
  • CPU Usage: Avoid heavy operations in message handlers; use worker threads or task queues.
  • Compression: Enable WebSocket compression to reduce data size.

Security

Secure WebSocket connections with authentication:

fastify.get('/ws', { websocket: true }, (connection, request) => {
    if (!request.session.user) {
        connection.close();
    } else {
        // Proceed with connection
    }
});

WebSockets and Socket.IO Integration

Socket.IO is a popular library providing a high-level abstraction over WebSockets, with fallbacks like long-polling when WebSocket is unavailable.

Installing Socket.IO

Install Socket.IO:

npm install socket.io

Setting Up Socket.IO Server

Create a Socket.IO server:

const io = require('socket.io')(3000);

io.on('connection', (socket) => {
    console.log('a user connected');

    socket.on('chat message', (msg) => {
        console.log('message: ' + msg);
        io.emit('chat message', msg);
    });

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

Connecting to Socket.IO Server

Include the Socket.IO client library:

<script src="/socket.io/socket.io.js"></script>
<script>
    const socket = io('http://localhost:3000');

    socket.on('chat message', (msg) => {
        console.log('received: ' + msg);
    });

    socket.emit('chat message', 'hello!');
</script>

Advanced Socket.IO Features

  • Rooms: Group connections for chat rooms or games.
  • Namespaces: Create isolated Socket.IO instances.
  • Binary Data: Support for Blob and ArrayBuffer.
  • Reconnection: Automatic reconnection on disconnect.
  • Compression: Reduce bandwidth usage.

Namespaces and Rooms Usage

Namespaces isolate services:

const ioChat = require('socket.io')(3001, { path: '/chat/socket.io' });
const ioNotifications = require('socket.io')(3002, { path: '/notifications/socket.io' });

ioChat.on('connection', (socket) => {
    // Chat events
});

ioNotifications.on('connection', (socket) => {
    // Notification events
});

Rooms group connections:

socket.on('joinRoom', (roomName) => {
    socket.join(roomName);
    socket.to(roomName).emit('welcome', 'Welcome to the room!');
});

Custom Handshake Authentication

Authenticate during the handshake:

const io = require('socket.io')(3000, {
    cors: {
        origin: '*',
        methods: ['GET', 'POST']
    }
});

io.use((socket, next) => {
    const token = socket.handshake.auth.token;
    if (token && validateToken(token)) {
        return next();
    }
    return next(new Error('authentication error'));
});

io.on('connection', (socket) => {
    // Authenticated connection
});

Error Handling and Monitoring

Monitor Socket.IO events:

socket.on('connect_error', (err) => {
    console.error('connect_error due to ' + err.message);
});

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

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

Performance and Load Balancing

Optimize for high concurrency:

  • Clustering: Use Node.js cluster module.
  • Load Balancing: Use Nginx for distributing connections.
  • Session Persistence: Use Redis for session data.

Socket.IO with Fastify

Combining Fastify’s high-performance framework with Socket.IO enables flexible real-time applications.

Installing Required Modules

Install Fastify and Socket.IO:

npm install fastify @fastify/socket.io

Integrating Socket.IO with Fastify

Register the fastify-socket.io plugin:

const fastify = require('fastify')();
const fastifySocketIo = require('@fastify/socket.io');

fastify.register(fastifySocketIo, {
    cors: {
        origin: true,
        credentials: true
    }
});

fastify.listen({ port: 3000 }, (err, address) => {
    if (err) throw err;
    console.log(`Server listening on ${address}`);
});

Using Socket.IO

Access the Socket.IO instance via fastify.io:

const io = fastify.io;

io.on('connection', (socket) => {
    console.log('a user connected');

    socket.on('chat message', (msg) => {
        io.emit('chat message', msg);
    });

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

Handling HTTP Requests and WebSocket

Combine HTTP and WebSocket handling:

fastify.get('/', async (request, reply) => {
    return { hello: 'world' };
});
Share your love