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.
- This repo treats chat as a relational system, so database rules do more work than the websocket layer ever could.
- Its duplicate-DM check and cascade behavior turn messy product edge cases into enforced state, not hopeful frontend logic.
- The auth flow is stricter than a typical tutorial app because refresh tokens are hashed and rotated.
- Media uploads stay stateless by skipping local disk and pushing buffers straight to Cloudinary.
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.
| Concern | Typical shortcut | ChatApplications approach | Why it matters |
|---|---|---|---|
| Direct messages | Create a new thread every time | Check whether the pair already has a conversation | Prevents duplicate DM clutter |
| Auth storage | Store refresh tokens as plain values | Hash refresh tokens before saving | Reduces damage if the database leaks |
| Deleted chats | Clean up rows manually | Use cascade rules in the schema | Keeps related data consistent |
| Uploads | Write files to local disk first | Keep files in memory and send to Cloudinary | Makes 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.
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!!!
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
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
| Concern | Typical shortcut | ChatApplications approach | Why it matters |
|---|---|---|---|
| Data model | Loose document collections | Relational tables with explicit joins | Better control over membership and cleanup |
| Direct messages | Create new chats freely | Reuse an existing conversation when found | Prevents duplicate threads |
| Token storage | Plain refresh token values | Hashed refresh tokens | Improves breach resilience |
| Uploads | Save to local disk | Stream buffers to Cloudinary | Deployment 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.





