A collaborative Kanban board with zero backend code — powered by RESTHeart Cloud.
| 🗂 Kanban board | Swimlanes × status columns: Open · In Progress · Blocked · Closed |
| 🖱 Drag & drop | Move cards across columns and reorder swimlanes |
| 👥 Group codes | Share an 8-char code with your team — no login required |
| 🔒 Server-side isolation | RESTHeart ACL ensures each group sees only its own data |
| ⚡ Real-time updates | Live sync via WebSocket change streams — changes by teammates appear instantly |
| 🙋 Assignees | Autocomplete from names already used in the group |
| 🏷 Tags & notes | Attach metadata to every task |
| 💾 Backup / restore | Export and import your group codes as JSON |
| 📱 Responsive | Works on mobile — task panel slides up as a bottom sheet |
| 🌙 Dark theme | Easy on the eyes |
| Layer | Technology |
|---|---|
| Frontend | Angular 21 (standalone components, signals) |
| Drag & drop | @angular/cdk/drag-drop |
| Backend | RESTHeart Cloud — managed REST + WebSocket API on MongoDB |
| Real-time | RESTHeart Change Streams over WebSocket |
| Styling | CSS custom properties · Inter font |
git clone https://github.com/SoftInstigate/todo-app.git
cd todo-app
npm install- Sign up at cloud.restheart.org — the free tier is enough.
- Create a new instance and wait for it to be provisioned.
- Open the instance dashboard and copy the Instance URL:
https://xxxx.eu-central-1-free-1.restheart.com - Note the root password you set during provisioning (also visible under Credentials in the dashboard).
The script requires HTTPie — install it with brew install httpie on macOS.
./scripts/init-backend.sh <instance-url> <root-user> <root-password># Example
./scripts/init-backend.sh https://xxxx.eu-central-1-free-1.restheart.com root mypasswordThe script sets up:
todosandswimlanescollections_schemasstore +todoJSON Schema validation- ACL rules for the
$unauthenticatedrole (see Security model)
Create src/environments/environment.prod.ts with your instance URL:
export const environment = {
restheartUrl: 'https://xxxx.eu-central-1-free-1.restheart.com',
};This file is listed in
.gitignore— your URL stays private.
For local development, edit src/environments/environment.ts instead.
ng serveOpen http://localhost:4200.
On first visit, create a group (generates a shareable 8-character code) or join one with an existing code. Share the code with your team — anyone who has it can access the same board. Use Export backup on the home screen to save your group codes and restore them on another device.
The board stays in sync across all open browsers without polling. When any teammate creates, updates, or moves a task, everyone else sees the change within milliseconds.
This works entirely through RESTHeart Change Streams — a WebSocket API backed by MongoDB Change Streams. The app opens two persistent connections on load, one for todos and one for swimlanes:
wss://your-instance.restheart.com/todos/_streams/changes?groupId=X&avars={"groupId":"X"}
wss://your-instance.restheart.com/swimlanes/_streams/changes?groupId=X&avars={"groupId":"X"}
The stream is filtered server-side. The change stream stage uses a $var reference so the server only pushes events that belong to the caller's group:
{ "$match": { "$or": [
{ "operationType": "delete" },
{ "fullDocument::groupId": { "$var": "groupId" } }
]}}$var: groupId is resolved at runtime from the avars query parameter — no unfiltered events ever leave the server. A dedicated ACL rule (priority 110) gates access to the stream endpoints:
(path-prefix("/todos/_streams") or path-prefix("/swimlanes/_streams"))
and qparams-contain(avars.groupId)
On the Angular side, a RealtimeService manages the sockets, debounces rapid bursts (500 ms), auto-reconnects on unexpected close, and exposes a connected signal that drives the live indicator (●) in the header.
The app sends no credentials. Access control is enforced entirely on the server by RESTHeart ACL.
Every request includes ?groupId=<code>. The ACL rules for both /todos and /swimlanes are:
| Property | Value |
|---|---|
| Role | $unauthenticated |
| Predicate | path-prefix("/todos") and qparams-contain(groupId) |
readFilter |
{"groupId": "@qparams['groupId']"} |
writeFilter |
{"groupId": "@qparams['groupId']"} |
mergeRequest |
{"groupId": "@qparams['groupId']"} |
- Requests without
groupIdare rejected (403). readFilter/writeFilterscope every read and write to the caller's group.mergeRequestauto-injectsgroupIdinto every new document — the client never sends it explicitly.
The group code is the shared secret. It is intentionally simple and suited for low-stakes team collaboration.
src/
app/
app.ts ← Kanban board component
app.html ← template
app.css ← dark theme styles
todo.service.ts ← CRUD for tasks
swimlane.service.ts ← CRUD for swimlanes
group.service.ts ← group code · backup · restore
realtime.service.ts ← WebSocket change streams · auto-reconnect
environments/
environment.ts ← dev (localhost)
environment.prod.ts ← production URL (git-ignored)
scripts/
init-backend.sh ← one-shot backend setup
This application was written entirely with Claude Code (Anthropic) — no code was written manually.
- The Angular frontend was scaffolded and developed through a conversation with Claude Code, which wrote all components, services, drag & drop logic, and styles.
- The RESTHeart Cloud backend was configured using the Sophia MCP server — an MCP tool that gives Claude Code direct access to the RESTHeart documentation. This allowed Claude to autonomously set up collections, JSON Schema validation, and ACL rules by querying the docs and running
httpiecommands.
Apache 2.0 — see LICENSE.