Personal ProjectWeb & Full-Stack~3 min read

Real-Time Chat App

Decoupled real-time messaging architecture using Next.js 15 and Socket.IO.

RoleFull-Stack Developer
TimelineJun — Jul 2025
DomainWeb & Full-Stack
Core StackNext.js 15, React 19, Socket.IO

What I Built#

I built this project to learn how to implement real-time bi-directional messaging with WebSockets. It is a full-stack chat application where users can join chat rooms, send direct messages, see live typing indicators, and view who is currently online.

The project is split into two repositories:

  • Frontend: Built with Next.js 15, React 19, and Tailwind CSS.
  • Backend: A standalone Node.js and Express server using Socket.IO and MongoDB.

Why Decouple the Backend?#

Next.js is often deployed on serverless environments (like Vercel), where server instances shut down after executing a request. Because WebSockets require a persistent, long-lived TCP connection, running Socket.IO directly inside Next.js serverless functions is unreliable.

By building a separate Node.js/Express backend, the WebSocket server can run continuously on a persistent host while the Next.js frontend handles page rendering and UI state.

Architecture & Event Flow#

architecture-diagram.txt
Next.js 15 Client (User A)               Next.js 15 Client (User B)
            │                                        │
            │ WebSocket Connection (Socket.IO)       │
            ▼                                        ▼
┌────────────────────────────────────────────────────────────┐
│                Express + Socket.IO Server                  │
│  ├── JWT Authentication & Socket Handshake                 │
│  ├── Room-Scoped Broadcasting ('send_msg', 'typing')       │
│  └── Connection / Disconnect Listeners (Presence)          │
└─────────────────────────────┬──────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────┐
│                     MongoDB Database                       │
│  (User accounts, chat rooms, and message history)          │
└────────────────────────────────────────────────────────────┘
  1. 01
    Authentication & Handshake: Users sign in with JWT authentication. When the frontend loads, it initiates a Socket.IO connection and passes the auth token to establish an authenticated socket session.
  2. 02
    Room Management: When a user selects a contact or group, the client emits a join_room event. The server registers the socket into a specific room ID so messages only broadcast to active participants of that conversation.
  3. 03
    Live Typing Indicators: As a user types, debounced typing and stop_typing events broadcast to other members in the room, displaying a live "User is typing..." indicator without saving to the database.
  4. 04
    Message Persistence: When a message is sent, the server broadcasts it to the room and commits it to MongoDB so past chat history loads when users return.

Challenges I Worked Through#

  • Tracking Online / Offline Presence: If someone closes their browser tab without explicitly logging out, a naive system leaves them marked as online. By listening to Socket.IO's native disconnect event on the server, the backend immediately detects when a socket drops and broadcasts an updated online roster to all users.
  • Optimistic UI Updates: Waiting for the server to acknowledge a message before displaying it in the sender's chat bubble creates noticeable input lag. I added optimistic rendering on the client so sent messages appear immediately in the conversation thread with a small pending icon, which confirms once the server acknowledges receipt.

What I Learned#

  • WebSockets need dedicated infrastructure: Keeping real-time socket connections on a separate server from stateless web frontends keeps both systems cleaner and easier to maintain.
  • Optimistic UI makes chat feel native: Adding messages to the local state immediately makes the interface feel as responsive as desktop messaging apps.

Complete Technology Stack

Next.js 15React 19Socket.IOExpressMongoDBTailwind CSS