Back to Blog
websocketnodejsjavascriptsoftware-engineeringweb-development

I Built a Production-Grade Real-Time Chat App as a CS Student — Here’s What I Learned

May 3, 20267 min readRead on Medium
I Built a Production-Grade Real-Time Chat App as a CS Student — Here’s What I Learned

The honest story behind PulseChat: WebSockets, AWS S3, WSO2 Asgardeo OAuth2, and a lot of late nights.

There’s a big difference between building a “chat app” and building a chat app.

Every tutorial I found showed me how to set up a Socket.io room in 20 minutes. Fine. But none of them told me how to handle file uploads at scale, how to show someone as “online” without melting your server, or — the one that genuinely stumped me for days — how to properly implement OAuth2 social login without rolling everything from scratch and hoping for the best.

That last one led me to WSO2 Asgardeo, an identity platform I hadn’t heard of before this project. It ended up being one of the most interesting technical decisions I made, and probably the one I learned the most from.

The result of all of it was PulseChat.

What is PulseChat?

PulseChat is a production-grade real-time chat application I built as a personal project. Not a tutorial clone. Not a proof-of-concept. Something I actually tried to build the right way, with real infrastructure decisions behind every feature.

Here’s what it does:

  • Real-time WebSocket messaging via Socket.io
  • Online presence indicators — see who’s actually active
  • Typing indicators — the little “…” that makes chat feel alive
  • Read receipts — know when your message landed
  • File sharing via AWS S3 + CloudFront
  • Google and GitHub social login powered by WSO2 Asgardeo OAuth2 — and this one deserves its own section

Stack: React · Node.js · PostgreSQL · Socket.io · AWS S3 · WSO2 Asgardeo

Why WSO2 Asgardeo? (And What Even Is It?)

Before PulseChat, authentication for me meant either rolling JWT from scratch or dropping in a managed service like Clerk. Both work — but neither really forced me to understand the OAuth2 flow underneath.

WSO2 Asgardeo is an identity-as-a-service platform built on open standards — OAuth2, OpenID Connect, SAML. It’s the kind of tool used in real enterprise and production systems, not just tutorial demos. When I found it, it felt like the right level of challenge: more involved than a plug-and-play SDK, but with enough structure that I wasn’t implementing OAuth from absolute zero.

The key thing Asgardeo does: it acts as the identity broker between your app and external providers like Google and GitHub. Your app never talks to Google directly. It talks to Asgardeo, and Asgardeo handles the rest.

The Part No One Warns You About: WebSockets Are Easy. Presence Is Not.

Getting a basic Socket.io connection working? Yeah, that’s the 20-minute tutorial part.

What they don’t show you is the edge case where a user closes their laptop without clicking “logout.” Or when someone’s on a bad connection and their socket drops and reconnects three times in five seconds. Do you flash their status offline twice? Do you wait? How long?

I ended up implementing a heartbeat system — the client pings the server on a timer, and the server marks you offline only after a missed window. It’s not glamorous code, but it’s the kind of logic that separates a demo from something people can actually use.

Typing indicators had a similar lesson. Naively, you’d emit a typing event on every keypress. Fire that at a server handling many connections and you're asking for trouble. The fix? Debounce it. Emit once when typing starts, stop after a short idle timeout. Small optimization, big difference.

File Sharing: Why I Reached for AWS S3 (and CloudFront)

When it came to file sharing, I had two options: store files on my server directly, or use object storage. I went with AWS S3 + CloudFront and it was the right call.

Storing files on your own server is fine until it isn’t. You hit disk limits, deployments get complicated, and serving files through your Node.js process adds unnecessary load. S3 takes all of that off the table.

CloudFront sits in front of S3 as a CDN, so files are delivered from an edge location close to the user — faster downloads, and your S3 bucket doesn’t need to be publicly exposed.

The trickiest part was generating pre-signed upload URLs on the backend so the client could upload directly to S3 without routing the file through my Node.js server. A bit of AWS IAM policy work, but once it clicked, it felt elegant.

Authentication: The WSO2 Asgardeo OAuth2 Flow I Finally Understood

This was the part of PulseChat I was most intimidated by, and the part I’m most proud of pulling off.

I used WSO2 Asgardeo to handle Google and GitHub social login via OAuth2. Here’s what the actual flow looks like when a user clicks “Login with Google”:

  1. Your React app redirects the browser to Asgardeo’s servers
  2. Asgardeo presents a login screen — Google, GitHub, or username/password
  3. The user authenticates with their chosen provider
  4. Asgardeo redirects back to your app with an authorization code in the URL
  5. A Callback.jsx component handles that redirect and exchanges the code for access + ID tokens
  6. The user is authenticated — and your app never saw their Google password once

That last point took me a while to really internalize. Your app is never the one talking to Google. Asgardeo is the trusted middleman. It decouples your application entirely from the identity provider, which is exactly how production auth systems are meant to work.

On the implementation side, Asgardeo provides a React SDK (@asgardeo/auth-react) that handles the token exchange automatically — but you still have to wire up the full flow yourself: the redirect, the Callback.jsx handler, the ProtectedRoute that checks isAuthenticated, and the navigation logic that kicks in once tokens are stored. Understanding why each piece exists made me a better developer.

MongoDB: Keeping It Flexible for Chat Data

I used MongoDB for PulseChat, and for a chat application it made a lot of sense. Chat data isn’t rigidly structured — messages can have text, attachments, reactions, or all three. A document model handles that naturally without you having to alter schemas every time a new message type shows up.

The flexibility also helped when storing things like read receipts and typing state alongside message documents. Instead of joining across multiple tables, related data just lives together. For the kind of rapid iteration a personal project involves, that’s a genuine advantage.

That said, working with MongoDB on this project made me think more carefully about when to reach for it vs a relational database. If PulseChat ever needed complex reporting or strict relational integrity across users and conversations, PostgreSQL would be the better call. But for a real-time chat app where the priority is fast writes and flexible document shapes? MongoDB earned its place in the stack.

What I’d Do Differently

If I rebuilt PulseChat today:

Redis for presence state. Right now, online/offline status lives in memory on the Node.js server. That works for a single instance. The moment you scale horizontally, each server has its own memory and you have a consistency problem. A shared Redis store solves this.

More aggressive error handling on the socket layer. Real-world sockets are unreliable. I’d invest in reconnection logic and message delivery guarantees from day one.

End-to-end encryption. For a chat app that takes privacy seriously, E2EE should be a first-class feature, not an afterthought.

Final Thoughts

PulseChat is still under maintenance, but as a learning project it delivered exactly what I hoped — exposure to the kind of problems tutorials skip over.

Real-time systems, cloud storage, OAuth2 flows with WSO2 Asgardeo, relational databases — these aren’t academic concepts anymore. I used all of them together, in one project, and that changes how you think about building software.

If you’re a CS student sitting on a basic CRUD app wondering what to build next — build something with WebSockets and real auth. You’ll hit hard problems fast. That’s the point.