ChatApplications: The Chat Backend That Puts SQL in Charge

A real-time messaging stack that uses relational constraints, token rotation, and stateless media handling to make conversations harder to break.

8 min read • View on GitHub • More from RISHIKARIR

A sealed vault built from interlocking database rings, with a chat thread at the center and a narrow socket pipe feeding into it from one side. The image explains that the system's real strength is the relational structure around the conversation, not the websocket layer alone.
The thesis in one frame: the chat app behaves like a durable relationship graph, not a loose bundle of sockets.
Key Takeaways

Most chat demos start with the socket and hope the rest follows. ChatApplications starts somewhere more disciplined: the data model. That choice changes everything. Conversations, memberships, admins, messages, and auth all behave like durable relationships, not disposable objects.

That is why this repo feels sturdier than a typical tutorial backend. The interesting part is not that messages move in real time. It is that the system works hard to prevent duplicate threads, orphaned records, and weak token handling before they become user-visible bugs.

Why This Chat App Feels Harder to Break

The key idea is simple. This project treats bad states as something the database should block, not something the UI should politely avoid. That shows up in conversation uniqueness, membership tables, cascade deletion, and the security posture around refresh tokens.

ConcernTypical shortcutChatApplications approachWhy it matters
Direct messagesCreate a new thread every timeCheck whether the pair already has a conversationPrevents duplicate DM clutter
Auth storageStore refresh tokens as plain valuesHash refresh tokens before savingReduces damage if the database leaks
Deleted chatsClean up rows manuallyUse cascade rules in the schemaKeeps related data consistent
UploadsWrite files to local disk firstKeep files in memory and send to CloudinaryMakes deployment more stateless

The Data Model Is the Product

The repository's most important design choice is its relational schema. Users, conversations, group metadata, admins, members, and messages are linked through explicit associations, which makes the backend behave like a graph with rules instead of a pile of loosely related tables.

The same schema that creates conversations also prevents duplicates and cleans up related data when a thread disappears.

That is the hidden win of using PostgreSQL and Sequelize here. The database is not only storing chat data. It is expressing business rules. A conversation is not just a row. It is a relationship with constraints.

How It Prevents Duplicate DMs

The teachable moment is the direct-message creation flow. Before creating a new thread, the controller checks whether the pair of users already shares an existing conversation. If it finds one, the backend returns that thread instead of minting another one.

const existingConversation = await Conversation.findOne({
  where: { isGroup: false },
  include: [{
    model: User,
    where: { id: { [Op.in]: [senderId, receiverId] } }
  }]
});

if (existingConversation) {
  return existingConversation;
}

const newConversation = await Conversation.create({ isGroup: false });
await newConversation.addUsers([senderId, receiverId]);

The value here is not algorithmic complexity. It is product hygiene. The backend makes a judgment call the moment the request arrives, so the UI does not have to guess whether a chat already exists.

Auth Is Treated Like an Attack Surface

I'm Rishika Rai, a Machine Learning Enthusiast passionate about Advanced Machine Learning Quantum Theories Neuroscience Psychology and Meta-Philosophy!!!

Rishika Rai, Author/Maintainer · Rishika70 (Rishika Rai) · GitHub

The authentication flow is stricter than the average demo stack. Login issues both access and refresh tokens, but the refresh token is hashed before it is stored. That matters because a database leak should not instantly become a session hijack.

The rotation pattern goes one step further. New access is generated by replacing old credentials rather than letting stale tokens linger. It is a small amount of extra ceremony that buys a lot of containment.

Real-Time Messaging Is the Delivery Layer

The Socket.io integration is important, but it is not the point of the project. Express is wrapped in http.createServer(app) so the websocket server can share the same process, and that keeps the transport layer neatly attached to the rest of the app.

const server = http.createServer(app);
initialiseSocket(server);
await seq.sync({ alter: true });
server.listen(PORT, () => {
  console.log(`Server running on ${PORT}`);
});

One caution is worth calling out. seq.sync({ alter: true }) is fine for development, but it is not the operational strategy you want for a serious production schema. It signals a working prototype with good instincts, not a finished migration discipline.

Files Move Without Touching Disk

A file passes from a user's hand into a small in-memory reservoir, then into a transformation chamber, and finally into a cloud archive tower. The image shows that uploads never land on local disk, which makes the media flow stateless and easier to deploy.
The media pipeline avoids local storage, which removes one of the most common deployment traps in small chat backends.

This is a scaling choice hiding inside an implementation detail. By using memory storage and Cloudinary, the app avoids writing temporary files to a local server that may not persist. That makes the upload path more portable, and it keeps the backend closer to stateless infrastructure.

Compared With Typical Chat Tutorials

ConcernTypical shortcutChatApplications approachWhy it matters
Data modelLoose document collectionsRelational tables with explicit joinsBetter control over membership and cleanup
Direct messagesCreate new chats freelyReuse an existing conversation when foundPrevents duplicate threads
Token storagePlain refresh token valuesHashed refresh tokensImproves breach resilience
UploadsSave to local diskStream buffers to CloudinaryDeployment is simpler and more portable

That comparison is the real story. This repo is not trying to be the smallest possible chat demo. It is trying to be harder to misuse. The cost is a little more structure. The payoff is much better behavior under pressure.

What It Gets Right, and What It Leaves Open

The modularity is strong. Controllers stay focused, the schema carries real meaning, and the file pipeline avoids common deployment traps. Even the rough edges tell you something useful: this is a serious prototype, not a polished product.

The open question is operational maturity. Schema sync-by-alter, development conveniences, and the absence of a few production hardening patterns all point to an active build, not a finished service. That is not a flaw. It is just the line between a smart architecture and a shipped platform.