Skip to content

vugarfamiloglu/Real-time-Team-Messaging-Platform

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Loop — real-time team messaging

A self-hosted Slack-style chat platform. Two services:

  • backend/ — Node.js (Express + ws) + TypeScript + SQLite. REST for reads/writes, WebSocket for live updates.
  • frontend/ — Vite + React + TypeScript + Tailwind. Premium dark theme with a coral accent.

Features in the box:

  • Workspaces with multiple users (real signup + JWT auth)
  • Public + private channels, member-scoped visibility
  • Direct messages (channels with kind=dm)
  • Threads — replies are messages with parent_id, surfaced in a right drawer
  • Live presence — online dots driven by WebSocket connection count per user
  • Typing indicator — "Alice is typing…" with 5 s expiry, dedupe per user
  • File sharing — drag/drop, paste image, server stores under uploads/
  • Reactions — toggle emoji, broadcast updated counts
  • Edit / delete own messages — both fan out via WS
  • Full-text search (SQLite FTS5) across every channel you're in (⌘K / Ctrl+K)
  • Reconnecting WS client with exponential backoff + auto re-subscribe
  • Heartbeat reaper — server pings every 25 s, terminates sockets that miss a pong, marks the user offline

Architecture

                                    ┌───────────────────────┐
                                    │  React + Vite (5173)  │
                                    │   ─ TanStack Query    │
                                    │   ─ zustand store     │
                                    │   ─ realtime client   │
                                    └─────────┬─────────────┘
                            HTTP /api/*        │        WebSocket /ws  (token via subprotocol)
                            ┌──────────────────┼──────────────────┐
                            ▼                                     ▼
                     Express routes                       ws/gateway.ts
                     (auth, REST CRUD,            ─ socket map (id → user, subs)
                      file upload, search)        ─ channel subscriptions
                            │                     ─ presence refcount per user
                            │                     ─ typing relay (excl. self)
                            └──────┬──────┐               │
                                   ▼      ▼               ▼
                              SQLite (WAL)          broadcastToChannel
                              ─ FTS5 mirror        broadcastToWorkspacesOfUser

Quick start (local dev)

Requires Node 18+.

# 1. Backend
cp backend/.env.example backend/.env
# Edit backend/.env: set JWT_SECRET to a long random string.

npm install
npm run seed     # creates the Acme workspace + 4 users with realistic chat
npm run dev:backend

# In a second terminal:
npm run dev:frontend

Open http://localhost:5173.

Sign in with any of the seeded users:

email handle password
alice@acme.dev alice loopdemo123
bob@acme.dev bob loopdemo123
carol@acme.dev carol loopdemo123
dave@acme.dev dave loopdemo123

To see live presence + typing, sign in as a different user in a second browser (or an incognito window). When you type in one window, the typing indicator shows in the other.

WebSocket protocol

Token auth via Sec-WebSocket-Protocol: bearer.<jwt>. Query-param fallback ?token= is accepted for tools like wscat.

Client → server envelopes:

type payload
subscribe { channel_ids: string[] }
unsubscribe { channel_ids: string[] }
typing { channel_id: string, parent_id?: string }

Server → client envelopes:

type payload
hello { user_id, online_user_ids: string[] }
message:created full message DTO (with reactions, files, reply_count)
message:updated full message DTO
message:deleted { id, channel_id, parent_id }
message:reactions { message_id, channel_id, reactions: [...] }
user:typing { user_id, channel_id, parent_id? }
user:online { user_id }
user:offline { user_id }

REST endpoints

All /api/* (except /api/auth/*) require Authorization: Bearer <jwt>.

Method Path Purpose
POST /api/auth/signup Create account, returns token + user
POST /api/auth/login Sign in
GET /api/workspaces Workspaces I'm in
POST /api/workspaces Create workspace (auto #general)
GET /api/workspaces/:id/members Users in workspace
GET /api/workspaces/:id/channels Channels + DMs I can see
POST /api/workspaces/:id/channels Create channel
POST /api/workspaces/:id/dms Open/reuse a DM with { user_id }
POST /api/channels/:id/join Join a public channel
POST /api/channels/:id/read Mark channel read
GET /api/channels/:id/messages ?before=<iso>&parent_id=&limit=
POST /api/channels/:id/messages Send message (multipart: body+files)
PATCH /api/messages/:id Edit own message
DELETE /api/messages/:id Delete own message
POST /api/messages/:id/react Toggle a reaction { emoji }
GET /api/files/:id Download attachment
GET /api/search?q=… FTS5 search across my channels
GET /api/online Current online user IDs

Design notes

  • Channels and DMs share one table (channels.kind ∈ {channel, dm}). Messages are uniform. The frontend renders a DM with the other user's name instead of a channel name. Simpler than two parallel models.
  • Threads are messages with parent_id. Top-level listing filters parent_id IS NULL; the thread drawer fetches parent_id = <message>. Reply counts are summarised in the SELECT for cheap rendering.
  • FTS5 mirror table + triggers keep search in sync without a separate indexing job. Tokenizer: unicode61 remove_diacritics 2 so "naïve" matches "naive".
  • Presence by refcount, not by ping. A user is online iff at least one of their sockets is alive. This handles "open 3 tabs, close 2" correctly.
  • Typing indicator is broadcast-excluding-self. The server drops the echo so the client UI doesn't have to special-case it.
  • Authorisation for every WS sub: caller must already be a member of the channel. A malicious client can't subscribe to a private channel by guessing the ID.
  • Every destructive action (delete message, sign out) goes through <ConfirmModal> — no window.confirm anywhere.

Layout

backend/
  src/
    main.ts                 Express bootstrap, mounts gateway
    env.ts, db.ts           sqlite schema + WAL + FTS triggers
    auth.ts                 bcrypt + JWT helpers, requireAuth middleware
    routes/
      auth.ts               signup, login, /me
      workspaces.ts         CRUD + members
      channels.ts           channels, join, DMs, read receipts
      messages.ts           list (cursor), send (multer), edit, delete,
                            reactions, file download, search (FTS5)
    ws/gateway.ts           the WebSocket gateway (presence, typing, broadcast)
    scripts/seed.ts         realistic seed

frontend/
  src/
    main.tsx                router + QueryClient
    index.css               Tailwind + design tokens
    lib/
      api.ts                fetch wrapper with bearer token
      auth.ts               localStorage session
      realtime.ts           reconnecting WS client, sub re-establish
      store.ts              zustand: onlineUsers + typing
      types.ts              shared TS types (envelopes, DTOs)
      format.ts             date helpers
    components/
      Modal.tsx, ConfirmModal.tsx, Avatar.tsx
    pages/
      SignInPage.tsx, SignUpPage.tsx
      WorkspacePicker.tsx
      AppShell.tsx          gates auth, wires WS, hosts cmd-k search
      Sidebar.tsx           workspace rail + channel/DM sidebar
      ChannelView.tsx       header, day-grouped message list, typing banner
      MessageRow.tsx        hover toolbar, reactions, edit/delete, files
      Composer.tsx          multiline + paste/drop + Enter-to-send + typing
      ThreadDrawer.tsx      reply pane
      SearchModal.tsx       cmd+k FTS5 results

What's intentionally not here

  • No video/voice. WebRTC is a different problem; we're chat-only.
  • No mobile push notifications. Browser desktop notifications could be wired in realtime.on('message:created') — left as an exercise.
  • No huddles / screen share. Same reason as above.
  • No SSO / SCIM. Self-hosted single-team scope.
  • No emoji picker library. Common-8 set in the hover toolbar; native emoji input works in the composer.

License

MIT.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages