Skip to content
AM
All projects
Real-time

Chatify Realtime Chat

A full-stack chat app with custom JWT auth, Socket.IO messaging, online presence, optional typing and notification sounds, Cloudinary image uploads, welcome emails through Resend, and Arcjet rate limiting in front of an Express REST API.

  • React
  • JavaScript
  • Node.js
  • Express.js
  • MongoDB
  • Socket.IO
  • Zustand
  • Tailwind CSS
  • JWT
  • Cloudinary

Architecture

Overview

Chatify is a one-to-one chat app: people sign up with email and password, see every other registered user as a contact, and exchange text and image messages in real time with online/offline presence. It is a single deployable unit: a React + Vite single-page app talks to an Express REST API and a Socket.IO server that share one Node.js HTTP server, with MongoDB (via Mongoose) for users and messages, Cloudinary for images, Resend for email and Arcjet in front of the API. In production Express also serves the built frontend, so the browser, API and socket all live on one origin.

System

text
React SPA (Zustand stores, axios, socket.io-client)
   |  REST /api/* (httpOnly JWT cookie)     |  WebSocket (same cookie)
   v                                        v
Express  --> Arcjet (shield, bots, rate limit) --> protectRoute
   |                                        |
   |  auth + message controllers   Socket.IO: auth middleware,
   |                               userId -> socketId presence map
   +--> MongoDB (User, Message)             ^
   +--> Cloudinary (avatars, chat images)   | emit "newMessage" to receiver
   +--> Resend (welcome email)   ------------+

Key design decisions

One cookie for REST and sockets. Login issues a 7-day JWT in an httpOnly, SameSite=strict cookie. A Socket.IO middleware parses that same cookie from the handshake headers and loads the user, so the token is never exposed to JavaScript and unauthenticated sockets are rejected before they connect.

Writes go through REST, sockets only push. Sending a message is a normal POST that validates the receiver, uploads any image and saves to MongoDB; only then does the server look up the receiver's socket and emit the saved document. Persistence and validation stay in one place, and the socket layer stays thin.

Presence from connection state. An in-memory map of userId to socketId is updated on connect and disconnect, and the full online list is broadcast to all clients. It is simple and needs no extra storage, at the cost of tracking one socket per user on a single server instance.

Optimistic sending. The chat store appends a temporary message immediately, then swaps in the server's copy or rolls back with an error toast if the request fails.

Edge protection before auth. Arcjet runs ahead of the auth middleware on every route, so floods and bots are rejected with 429/403 before any database lookup. It fails open if Arcjet itself errors, favouring availability.

Stack

  • Frontend: React 19, Vite, React Router, Zustand, Tailwind CSS with DaisyUI, react-hot-toast
  • Backend: Node.js, Express, Socket.IO, jsonwebtoken, bcryptjs
  • Data: MongoDB with Mongoose
  • Integrations: Cloudinary, Resend, Arcjet
  • Hosting: single Node service serving the API, sockets and built SPA

Key Implementation Highlights

  1. Implemented JWT auth from scratch with bcrypt hashing and an httpOnly, SameSite=strict cookie instead of a third-party auth provider.

    Security
  2. Authenticated Socket.IO connections by parsing the same JWT cookie from the handshake, rejecting sockets without a valid user.

    Realtime
  3. Pushed newly saved messages straight to the receiver's socket and broadcast the online-user list on every connect and disconnect.

    Realtime
  4. Added optimistic message sending in a Zustand store, with rollback and an error toast when the request fails.

    Frontend
  5. Placed Arcjet shield, bot detection and a 100-requests-per-minute sliding-window limit ahead of authentication on every API route.

    Security
  6. Uploaded profile pictures and chat images to Cloudinary from base64 data, storing only the secure URL in MongoDB.

    Media
  7. Sent an HTML welcome email through Resend after signup without letting email failures break account creation.

    Integration
  8. Built optional keystroke and notification sounds with a toggle remembered in localStorage.

    Frontend
  9. Served the built React app from Express in production so the API, sockets and frontend share one origin.

    DevOps

Interested in the technical details?

Let's Talk