Back to Projects
React 19TypeScriptViteTailwind CSSSocket.IONode.jsExpressBetter-SQLite3TanStack QueryZodVitest

UniShare

A university community platform where students list items, form groups, and message each other. The design constraint was that everything social should feel immediate: a new message or listing appears without a refresh, and without the app polling in the background.

Full-Stack Developer
UniShare

The Problem

Campus buying, selling, and group coordination scatter across Facebook groups and messenger threads, where posts are unsearchable and nothing is scoped to the university. Rebuilding that on request-response alone would mean constant polling to feel current.

The Solution

One Socket.IO connection per client carries chat, notifications, and listing updates, with REST handling ordinary reads and writes. TanStack Query owns the cache, so a socket event invalidates the affected query rather than the UI hand-patching its own state.

System Architecture

Better-SQLite3 backs the server — synchronous and embedded, which removes connection-pool handling and async plumbing at this scale. Zod validates payloads at runtime on the client, so data crossing the socket boundary is checked rather than trusted to match its TypeScript type.

UniShare holds one Socket.IO connection per client, so listings, chat, and notifications all update without polling.CLIENT - REACT 19 + VITEReact Router DOMroute-level code splitTanStack Querycache + invalidationZod Schemasruntime validationREST for reads, WebSocket for live updatesTRANSPORTExpress RESTCRUDSocket.IOchat + notificationssingle duplex channelSERVER - NODE.JSRoute Handlerslistings, groups, savesSocket Event Busroom-scoped broadcastBetter-SQLite3synchronous, embedded
UniShare holds one Socket.IO connection per client, so listings, chat, and notifications all update without polling.Scroll sideways to see the full diagram

Key Features

Innovative solutions developed to solve complex technical challenges.

Live chat and notifications

Socket.IO carries messages and notifications over one duplex connection, with rooms scoping broadcasts to the people who should receive them.

Cache-driven UI updates

Socket events invalidate TanStack Query keys instead of mutating component state, so the refetch path is the same whether an update arrived by socket or by navigation.

Runtime-validated payloads

Zod schemas check incoming data at the boundary, catching shape mismatches that TypeScript types cannot enforce at runtime.

Tested and continuously deployed

Vitest with React Testing Library and JSDOM cover the UI, and GitHub Actions handles deployment.

Challenges & Breakthroughs

1

Challenge: Live updates and a client cache disagree easily — the socket says one thing while the cache still holds another.

Growth: Stopped treating socket payloads as state and treated them as invalidation signals instead. Letting the query layer refetch keeps one source of truth; patching the cache directly from an event is where the two views drift apart.

2

Challenge: Choosing a database for a project that must be trivial to run locally but still relational.

Growth: Better-SQLite3 is synchronous and embedded, so there is no pool, no daemon, and no await on every query. At campus scale the simplicity is worth more than the concurrency a server-based database would add.

Results & Impact

1 socket
Transport
Chat, notifications, and listing updates share one connection
96 files
Frontend
TypeScript throughout, with route-level code splitting
Automated
CI
Vitest suite plus GitHub Actions deployment

Next Project

ChefMate