Skip to content

Chat System

Real-time chat system using WebSockets for communication between customers and sellers about orders or general inquiries.

Overview

  • WebSocket-based real-time messaging
  • Order-specific chats and general chats
  • File attachments support
  • Chat archiving functionality
  • Typing indicators and presence
  • Message history with pagination

WebSocket Gateway

@WebSocketGateway({
  cors: { origin: '*' },
  namespace: '/chat',
  maxHttpBufferSize: 10e6 // 10 MB
})
export class ChatGateway {
  @WebSocketServer() server: Server;

  async handleConnection(client: Socket) {
    // Verify JWT token
    const token = client.handshake.auth.token;
    const payload = await this.authService.verifyAccessToken(token);
    client.data.user = payload;
  }

  @SubscribeMessage('join')
  async handleJoinChat(
    @MessageBody() data: { chatId: string },
    @ConnectedSocket() client: Socket
  ) {
    // Verify membership
    if (!(await this.chatRepository.isUserInChat(data.chatId, client.data.user.id))) {
      client.disconnect();
      return;
    }

    client.join(`chat:${data.chatId}`);
    this.server.to(`chat:${data.chatId}`).emit('user joined', {
      userId: client.data.user.id,
      chatId: data.chatId
    });
  }

  @SubscribeMessage('chat message')
  async handleMessage(
    @MessageBody() data: {
      chatId: string;
      content: string;
      attachments?: File[];
    },
    @ConnectedSocket() client: Socket
  ) {
    const user = client.data.user;

    // Save attachments
    let attachmentFiles = [];
    if (data.attachments) {
      attachmentFiles = await Promise.all(
        data.attachments.map(file =>
          this.storageService.upload(file, UploadObjectType.CHAT, data.chatId)
        )
      );
    }

    // Persist message
    const message = await this.chatRepository.addMessage({
      id: generateId(),
      chatId: data.chatId,
      senderId: user.id,
      content: data.content,
      timestamp: new Date(),
      attachments: attachmentFiles
    });

    // Broadcast to chat room
    this.server.to(`chat:${data.chatId}`).emit('message', {
      ...message,
      sender: {
        id: user.id,
        fullName: user.fullName
      }
    });
  }
}

Client Usage

Connect

import io from 'socket.io-client';

const socket = io('http://localhost:3000/chat', {
  auth: {
    token: accessToken
  }
});

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

Join Chat

socket.emit('join', { chatId: 'chat-123' });

socket.on('user joined', (data) => {
  console.log(`${data.userId} joined chat`);
});

Send Message

socket.emit('chat message', {
  chatId: 'chat-123',
  content: 'Hello!',
  attachments: [] // Optional file attachments
});

Receive Messages

socket.on('message', (message) => {
  console.log(`${message.sender.fullName}: ${message.content}`);
  displayMessage(message);
});

REST API (for chat history)

List Chats

GET /chats?page=1&limit=20
Authorization: Bearer <access_token>

Get Chat Messages

GET /chats/:id/messages?page=1&limit=50
Authorization: Bearer <access_token>

Archive Chat

POST /chats/:id/archive
Authorization: Bearer <access_token>

Best Practices

✅ Do

  • Verify user membership before joining chat
  • Authenticate WebSocket connections with JWT
  • Store message history in database
  • Support file attachments
  • Implement message pagination
  • Show typing indicators

❌ Don't

  • Don't allow joining chats without verification
  • Don't forget to disconnect unauthorized users
  • Don't allow messages from non-members
  • Don't store large files inline
  • Don't skip message persistence