Full-stack · 2026

TaskFlow

Server-authoritative task manager on Next.js 16, PostgreSQL and Drizzle

Live

Overview

  • What: a task manager for people juggling several projects, with priorities, due dates, tags, a calendar and a dashboard.
  • Hardest problem: keeping it server-authoritative: every button performs a real database operation and every dashboard number is derived from a query.
  • How it is validated: 147 Vitest tests, each on a real in-memory PostgreSQL with real migrations, plus 39 Playwright tests against a production build.

It runs on embedded PostgreSQL (PGlite) with no setup, or any PostgreSQL 13+ in production. Demo accounts are listed in the README.

Architecture

 browser --> Next.js 16 (App Router, RSC, server actions)
                |  session cookie (httpOnly, HMAC-hashed token)
                v
        Drizzle ORM: every query scoped by WHERE user_id = ?
                |
        PostgreSQL (PGlite locally / any PostgreSQL 13+ in production)
 filters, sort, search, pagination: URL state --> SQL
fig. 01 — TaskFlow architecture

Highlights

  • Authorisation lives in the WHERE clause (eq(tasks.userId, userId) on every query), so another user's row and a missing row are both a 404; tests/authorization.test.ts asserts cross-user isolation from the outside.
  • Custom session auth: scrypt (N=16384) password hashes, 256-bit tokens stored only as HMACs, httpOnly SameSite=Lax cookies, timing-matched failed logins, fixed-window rate limits.
  • Filter, sort, search and pagination state live in the URL and execute as SQL (stable tiebreak, LIKE-wildcard escaping, 300 ms debounce); time-zone-correct "overdue / due today" across DST and half-hour offsets.
  • Hand-built accessible UI primitives (~900 lines: dialog focus trap, arrow-key menus, live-region toasts) and server-rendered inline SVG charts.
  • 147 Vitest tests across 8 files, each booting a real in-memory PostgreSQL and running real migrations; 39 Playwright tests (desktop, mobile, auth flows) against a production build.
  • Seven tables, eight composite indexes led by user_id; Zod 4 schemas shared by client and server.

What I learned

Putting authorization in the WHERE clause, so a foreign row and a missing row are both a 404, removed a whole class of leaks and made the cross-user test simple to write. Testing against a real in-memory PostgreSQL with real migrations caught things mocks would have hidden.