Web AppQ2 2022Lead
Jsswire
Chat application connecting students with fellows and seniors across their campus.

The build
A real-time extension of Jssconnect — and the project that taught me which of my assumptions were load-bearing.
Problem
Jssconnect was request-response: students opened a page, took what they needed, and left. What it couldn't do was put a first-year in touch with a senior who'd already solved the thing they were stuck on. That conversation was happening in group chats the platform couldn't see.
Constraints
- It had to sit on top of an app that wasn't built for it. Jssconnect was stateless by design. Real-time isn't.
- One identity, not two. Making students sign in again for chat would have killed it. The existing Passport session had to carry over.
- Scoped to one campus. Presence and rooms only make sense if the population is bounded — an open chat would have been a different, worse product.
Approach
Socket.IO for rooms and direct threads, with the handshake sharing the Express session so the socket knows who is connecting without a second auth path:
io.on("connection", (socket) => {
socket.on("join", ({ room, user }) => {
socket.join(room);
presence.touch(user, room); // TTL-backed
io.to(room).emit("presence", presence.of(room));
});
socket.on("disconnecting", () => presence.drop(socket));
});Tradeoffs
- Rooms are cheap; presence is not. Joining a room is one line. Knowing who's actually online is the hard part — a closed laptop lid doesn't fire a disconnect, so users linger as "online" for as long as the transport allows. Heartbeats plus a short server-side TTL got presence honest enough to display, at the cost of a background timer that's always running.
- Sharing the Express session with the socket handshake. Avoided the one thing I least wanted to maintain — a second auth path. It also couples the chat to the host app's session store permanently.
- Messages as documents keyed by room, paged by timestamp. Simple and correct, and it degrades: once a thread gets long, every scroll-up is a fresh query.
Outcome
The feature shipped and students used it. The schema is the part I'd redraw first — an append-only log per conversation with a cached tail, and read receipts as their own small collection instead of a field mutated on every open.
Decisions
- Retrofitting onto Jssconnect over building a separate app. Chosen because the users, the identity, and the trust were already there — a chat app with nobody in it is worthless. Against it: every real-time assumption had to fit inside a codebase built on the opposite one.
- Socket.IO over raw WebSockets. Chosen for rooms, reconnection, and transport fallback out of the box, which on a campus network mattered. Against it: a heavier client and a protocol that isn't quite WebSocket when you need to debug it.
- TTL-based presence over trusting disconnect events. Chosen because the disconnect event is a lie often enough to be visible. Against it: presence is now eventually consistent, and a user can appear online for a few seconds after they've gone.
Stack
- Node.js
- Express.js
- Socket.IO
- MongoDB
- Passport.js
- Materialize CSS
Related projects

Lecture Lense
Turns any recording into searchable, timestamp-cited notes.

Chitter
A mini social app that lets users sign up, log in and post whatever is on their mind.

Heckfree
Users get a public profile that showcases all of their links in one place.