All projects

Case study

KanbanHub

A team workspace with Trello-style boards, time tracking and live updates, five ways to sign in and per-device sessions, built and deployed end to end as a solo project.

Role
Solo project: product, design, frontend, backend and deployment
Timeline
Jun 2025 – Oct 2026
Stack
ReactZustandTailwind CSSNode.jsExpressSocket.IOMongoDBFirebase AuthWebAuthnZodPlaywright
KanbanHub board with a photo background, the time tracking panel of a card and the mobile app
automated tests
215
automated tests
end-to-end browser flows
28
end-to-end browser flows
sign-in methods, incl. passkeys
5
sign-in methods, incl. passkeys
commits since June 2025
80
commits since June 2025

The problem

Small teams scatter ownership, progress and time across chats, spreadsheets and boards nobody updates. KanbanHub keeps them in one place: every card has people, dates, a checklist and the hours spent on it, and the board updates live for everyone on it.

Boards can be private, shared with the workspace or opened through a public link, so access control is part of every feature: the API, the live connection, invite links and email notifications all check it.

What I built

  • Trello-style boards with photo or colour backgrounds; lists you can colour, fold, sort, copy and move
  • Cards with covers, labels, priorities, dates, checklists, attachments, watchers and comments
  • Drag and drop written from scratch on pointer events, for mouse and touch, with edge auto-scroll and FLIP animations
  • Time tracking: a timer per person, hours added by hand, estimates and totals per person
  • Live updates and presence over Socket.IO, so changes appear without a refresh
  • My tasks, a calendar with drag to reschedule, a team view with workload, a card filter and ⌘K search
  • Sharing by email with roles, invite and public view links, and ownership transfer
  • Notifications in the app, as desktop pop-ups and by email, instantly or as an hourly digest
  • Email and password, Google, Microsoft, Apple and passkey sign-in, with a list of signed-in devices
  • Light and dark themes; English, Ukrainian and Russian interface

Screens

Board

Lists with labels, priorities, dates, checklists and a live timer on the card in progress. Changes sync for everyone on the board.

Board on a phone
Card on a phone
My tasks on a phone

Architecture

A React single-page app talks to an Express API over REST and Socket.IO. Both are deployed on Render under kanbanhub.app and api.kanbanhub.app; data lives in MongoDB Atlas.

  1. 01

    Client

    • React + Vite, Tailwind CSS
    • Zustand state
    • Own pointer-based drag and drop
    • Socket.IO client
    • Firebase Auth and SimpleWebAuthn
  2. 02

    API

    • Express REST API with Zod validation
    • Socket.IO rooms and presence
    • JWT in HttpOnly cookies, one session per device
    • Helmet, CSRF origin checks, rate limits
    • Background jobs: due reminders and email digests
  3. 03

    Data and services

    • MongoDB Atlas with transactions
    • Firebase Admin for provider tokens
    • Brevo transactional email
    • Cloudinary for avatars and images
    • Unsplash, Picsum and Wikimedia backgrounds

Engineering decisions

01

Realtime that respects permissions

Sockets authenticate with the same HttpOnly cookie as the REST API. Joining a board room requires ownership or membership, and every heartbeat re-checks the session, so a revoked session also loses live updates. People show as away after 45 seconds without a heartbeat.

02

A session per device

Each sign-in creates a session record named in the cookie, so the settings page lists every device and can sign out one or all the others. A signed-out device drops its live connection at once. Changing the password ends every other session and keeps the current one.

03

Time that is counted once

A person has one running timer: starting another one stops the first. A timer is finished with a conditional update, so two stop requests at the same moment log the time only once, and the card total is recalculated from the entries.

04

Safe under concurrent requests

Two signups for the same email, or two uses of one password-reset token or passkey challenge, can only succeed once. Removing a member is one MongoDB transaction: membership, their notifications, task assignments and open invitations are cleaned up together.

05

Links that expire when access changes

Public and invite links are stored as hashes with a version. Changing a board’s visibility or revoking a link bumps the version, so old links stop working immediately.

06

Sign-in bound to the provider account

Google, Microsoft and Apple sign-in is bound to the provider’s account ID, not the email, so another account claiming the same address cannot take over. Provider tokens must be fresh, unrevoked and for a verified email, and linking needs the current password.

07

Email that does not spam

Activity can arrive instantly, capped at 30 emails an hour, or as an hourly digest grouped by board. The digest waits five minutes to gather a burst of changes, and each user’s digest is claimed first so two servers never send it twice.

08

Drag and drop without a library

Cards and lists move with pointer events for both mouse and touch: a lifted ghost, a drop slot, auto-scroll near the edges and FLIP animations when cards land. On touch screens nothing opens the keyboard by itself.

Testing

152 backend integration tests run on Node’s built-in test runner with Supertest against an in-memory MongoDB, and 63 frontend unit tests cover card filters, sorting and display rules. 28 Playwright flows drive the real app in a browser, from sign-up to sharing a board. A few of the backend cases:

  • logs time only once when a timer is stopped twice at the same moment
  • never signs in a different provider account that claims the same email
  • ends every other session and keeps this device signed in
  • issues passkey sign-in challenges without an email and accepts each one only once
  • invalidates a general invite link after room visibility changes
  • allows a reset token to be claimed only once under concurrency

Want to talk about this project?

I’m happy to walk through the code, the trade-offs and what I would build next.

Get in touch