Skip to content
abdalrhman.

Real-time chat app · 07 / 09

MERN Chat

One-to-one real-time chat with cookie-based JWT auth, live presence, image messages and 32 themes.

Role
Solo developer
Year
2025
Status
Shipped
Platform
Web
MERN Chat sign-in page in the dark orange theme

Overview

The project.

A full-stack messaging app on the MERN stack. People sign up, see who is online and exchange text and image messages that arrive instantly, without a refresh.

It runs as a single Render service: one Express process serves the REST API, the Socket.IO server and the built React app from the same origin, which keeps cookie auth simple and hosting cheap.

The problem

A chat app has to feel instant and stay secure: real accounts, live delivery and presence, and media, without a heavy multi-service deployment.

My solution

REST for auth, history and sending; Socket.IO for push delivery and presence; httpOnly JWT cookies for sessions; Cloudinary for media; all served from one Express process.

What it does

Key features.

  • 01

    Secure accounts

    Sign-up and login with validation, bcrypt-hashed passwords and a 7-day JWT in an httpOnly, SameSite-strict cookie. Sessions restore on reload.

  • 02

    Instant messaging

    Each message is saved first, then pushed over Socket.IO only to the recipient's socket, so the open conversation updates live.

  • 03

    Online presence

    A live list of connected users drives status dots and a “show online only” filter in the contacts sidebar.

  • 04

    Image messages

    Attach an image with a preview before sending. Files go to Cloudinary and are stored as URLs.

  • 05

    Profiles

    Avatar upload from the profile page, stored on Cloudinary.

  • 06

    32 themes

    A DaisyUI theme picker with a live chat preview, remembered in localStorage.

Interface

Theme settings with 32 themes and a live chat preview
Create account page

Under the hood

How it's built.

Architecture and the engineering decisions behind it.

  1. 01

    One origin, one service

    Express and Socket.IO share one HTTP server. In production Express also serves the Vite build with an SPA fallback, so the client calls a relative /api and the cookie flow stays same-origin.

  2. 02

    REST and sockets, each for its job

    Sending is a REST call that persists to MongoDB; Socket.IO only notifies. A userId-to-socket map routes newMessage events and broadcasts presence on connect and disconnect.

  3. 03

    Small client stores

    Zustand stores for auth and socket, chat and theme. The chat store attaches one listener per open conversation and removes it on switch.

  4. 04

    Lean data model

    Two Mongoose models, User and Message. A conversation's history is a single query on the sender and receiver pair.

Problems solved

Technical challenges.

  • SPA routing broke in production

    An Express 5 upgrade broke the catch-all route that serves the React app. I moved back to Express 4 to keep the production SPA fallback reliable.

  • Cookies across dev and production

    CORS origin and the API and socket base URLs switch by environment. Production is same-origin, so the SameSite-strict cookie works without third-party cookie problems.

  • No duplicate real-time handlers

    The client subscribes to new messages only for the open conversation and cleans up on change, so a message never renders twice.

Development status · Shipped

What's next.

Everything above is built and working. These parts are planned and not implemented yet:

  • Group conversations
  • Unread counts and notifications for other chats
  • Typing indicators and read receipts
  • Paginated message history