Skip to content

wiktor-cl/collabboard

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CollabBoard

CI License: MIT

A real-time collaborative kanban board. Multiple people open the same board and see every change — cards created, edited, dragged between lists, deleted — the instant it happens, along with live presence and cursors of everyone else in the room.

Built to demonstrate a full-stack system end to end: a typed React front end, a FastAPI back end, WebSocket realtime with Redis pub/sub for horizontal scaling, JWT auth, PostgreSQL persistence, a test suite, and one-command Docker startup.

Stack: React 18 · TypeScript · Vite · Tailwind · FastAPI · SQLAlchemy 2 · PostgreSQL · Redis · WebSockets · Docker Compose


What it does

  • Boards, lists, and cards — create boards (each seeded with To do / In progress / Done), add lists and cards, edit card titles and descriptions, drag cards within and across lists.
  • Realtime sync — every mutation is broadcast over a WebSocket to everyone viewing the board. No refresh, no polling.
  • Presence — avatars of who is currently on the board, with a live/reconnecting status indicator.
  • Live cursors — see other people's pointers move across the board in real time.
  • Sharing — invite a teammate by email to give them access to a board.
  • Auth — register / login with hashed passwords (bcrypt) and JWT bearer tokens.

Architecture

┌──────────────┐      REST (JSON)      ┌───────────────────────────┐
│   React SPA   │ ────────────────────▶│         FastAPI            │
│  (Vite + TS)  │                       │  auth · boards · cards    │
│               │ ◀════ WebSocket ═════▶│  WebSocket room manager   │
└──────────────┘   events + presence   └────────────┬──────────────┘
                                                     │
                                        ┌────────────┴────────────┐
                                        │                         │
                                  ┌───────────┐            ┌──────────────┐
                                  │ PostgreSQL │            │    Redis     │
                                  │ (SQLAlchemy)│           │  pub/sub for │
                                  │ persistence │           │ multi-instance│
                                  └───────────┘            └──────────────┘

The interesting part is the realtime layer. A single backend process holds in-memory "rooms" (one per board) and broadcasts events to the sockets in each room. To scale beyond one process, every instance publishes its events to a shared Redis channel and subscribes to it, so a change made on instance A reaches clients connected to instance B. If Redis is unavailable the backend degrades gracefully to single-instance broadcasting — the app still works, it just doesn't fan out across replicas.

State is server-authoritative: the client sends an action over REST, the server persists it and broadcasts the resulting event, and the client updates its UI when that event arrives over the socket — including for the user who triggered it. One source of truth, no divergence between clients.

Run it

With Docker (recommended)

git clone https://github.com/wiktor-cl/collabboard.git
cd collabboard
docker compose up --build

Then open http://localhost:8080. Postgres, Redis, the API, and the web app all start together. To see realtime in action, open the same board in two browser windows (or invite a second account) and start moving cards.

Local development

Backend

cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Point DATABASE_URL at a running Postgres, or use SQLite for a quick spin:
export DATABASE_URL="sqlite:///./dev.db"
export JWT_SECRET="dev-secret"
uvicorn app.main:app --reload

API docs are served at http://localhost:8000/docs.

Frontend

cd frontend
npm install
npm run dev

The Vite dev server (http://localhost:5173) proxies /api and /ws to the backend on port 8000, so there is no CORS or config to wire up.

Tests

The backend has a pytest suite covering auth, access control, board membership, invites, and the card create / move / reorder / delete logic (including position compaction across columns).

cd backend
pip install -r requirements.txt
pytest

Tests run against in-memory SQLite, so no external services are required.

API overview

Method Path Description
POST /api/auth/register Create account, returns a JWT
POST /api/auth/login Log in, returns a JWT
GET /api/auth/me Current user
GET /api/boards Boards the user belongs to
POST /api/boards Create a board (+ default lists)
GET /api/boards/{id} Full board with lists + cards
POST /api/boards/{id}/invite Grant a user access by email
POST /api/boards/{id}/columns Add a list
POST /api/boards/{id}/cards?column_id= Add a card
POST /api/boards/{id}/cards/{cid}/move Move / reorder a card
WS /ws/boards/{id}?token= Realtime board channel

Project layout

collabboard/
├── .github/workflows/ci.yml   # pytest + frontend build on every push/PR
├── backend/
│   ├── railway.json           # backend deploy config (Railway)
│   ├── app/
│   │   ├── main.py            # app wiring + lifespan
│   │   ├── models.py          # SQLAlchemy models
│   │   ├── schemas.py         # Pydantic I/O
│   │   ├── security.py        # bcrypt + JWT
│   │   ├── deps.py            # auth + board-access dependencies
│   │   ├── realtime.py        # WebSocket room manager + Redis pub/sub
│   │   └── routers/           # auth, boards, columns, cards, ws
│   └── tests/                 # pytest suite
├── frontend/
│   ├── vercel.json            # frontend deploy config (Vercel)
│   └── src/
│       ├── api/client.ts      # typed API client (configurable base URL)
│       ├── auth/              # auth context
│       ├── hooks/useBoardSocket.ts  # realtime hook (presence, cursors, reconnect)
│       ├── pages/             # Login, Boards, Board
│       └── components/        # Column, Card, PresenceBar, CursorLayer
└── docker-compose.yml

Notes & trade-offs

  • The schema is created with create_all on startup to keep the demo a single command. A production deployment would use Alembic migrations.
  • Card positions are stored as integers and compacted on move/delete. For very high-churn boards a fractional-index scheme would avoid rewrites.
  • Cursor positions are ephemeral — broadcast but never persisted.

Deployment

The app deploys as a split: the FastAPI backend on Railway (with managed Postgres + Redis) and the React frontend on Vercel. The frontend reads the backend's URL from build-time env vars, so the two talk cross-origin over plain HTTPS/WSS — no proxy in the middle.

Backend → Railway

  1. New project → Deploy from repo. Set the service's Root Directory to backend (it uses railway.json and the Dockerfile).
  2. Add the PostgreSQL and Redis plugins. Railway injects DATABASE_URL and REDIS_URL automatically. (The DATABASE_URL arrives as postgresql://…; the app rewrites it onto the psycopg v3 driver itself, so there is nothing to configure.)
  3. Add service variables: JWT_SECRET (a long random string) and CORS_ORIGINS set to your Vercel URL, e.g. https://collabboard.vercel.app.
  4. Railway health-checks /api/health and gives you a public URL.

Frontend → Vercel

  1. Import the repo. Set Root Directory to frontend (Vercel detects Vite via vercel.json).
  2. Add environment variables pointing at the Railway backend:
    • VITE_API_BASE = https://<your-app>.up.railway.app
    • VITE_WS_BASE = wss://<your-app>.up.railway.app
  3. Deploy. Update the backend's CORS_ORIGINS to match the final Vercel domain.

Locally, leave those vars empty — the Vite dev proxy and the Docker/nginx setup both keep the front and back on one origin.

License

MIT — see LICENSE.

About

Real-time collaborative kanban board with live presence and cursors. FastAPI, WebSockets, Redis pub/sub, React, TypeScript.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors