Skip to content
All projects

05Productivity · Full-stack App

Task & Time Tracker

A full-stack time tracker whose clock stays honest across tabs, reloads, devices and midnight.

Role
Full-stack Engineer
Context
Personal project · 2026
Primary stack
  • Next.js
  • TypeScript
  • Express
  • PostgreSQL
  • Prisma
  • TanStack Query
Task & Time Tracker — Personal project

Tasks, timer and analytics, layered

01

Overview

A small productivity app: write down tasks, run a timer while you work on them, and see where the time went each day and week. A Next.js front end talks to an Express and PostgreSQL API, in one npm-workspaces repo. The feature list is short on purpose — the effort went into the parts that are easy to get subtly wrong.

02

Problem

Time trackers fail quietly. A browser clock drifts or sleeps, two tabs start two timers, a session runs past midnight or through a daylight-saving change — and the numbers stop adding up. Get any of it wrong and the one thing the product promises, an honest record of time, is gone.

03

My role

  • Designed and built the whole product: Next.js front end, Express API, PostgreSQL schema and deployment.
  • Pushed every rule I could into the database — a partial unique index, a composite foreign key and CHECK constraints, hand-written into versioned migrations.
  • Wrote the daily and weekly analytics as timezone-aware SQL instead of client-side arithmetic.
  • Built cookie-based auth with Argon2id hashing and refresh tokens that rotate on every use.
  • Added an optional AI helper that turns a rough note into a clean task title and description, with Gemini or Claude behind one interface.
04

Architecture

  1. FrontendNext.js 16 and React 19 with Tailwind v4 and shadcn/ui. TanStack Query is the only client-side copy of server state; forms run on React Hook Form and Zod.
  2. APIExpress 5 in four modules, each shaped routes → controller → service → repository. An architecture test fails the build if a layer reaches past its neighbour.
  3. DatabasePostgreSQL through Prisma 6, with the constraints the app depends on written as raw SQL in the migrations. CI replays every migration and fails on drift.
  4. SharedOne package of Zod schemas, response types and formatters used by both apps — and the source of the generated OpenAPI document.
  5. HostingWeb on Vercel, API on Render. Next.js proxies the API path so session cookies stay first-party.
05

Technology

Interface

  • Next.js
  • React
  • TypeScript
  • Tailwind CSS
  • shadcn/ui
  • TanStack Query
  • React Hook Form
  • Zod

API & data

  • Express
  • Prisma
  • PostgreSQL
  • Argon2id
  • JWT
  • pino

AI

  • Gemini
  • Claude

Delivery

  • Vercel
  • Render
  • npm workspaces
  • OpenAPI
06

Engineering challenges

C1

A timer the browser can't get wrong

Client clocks drift, sleep and can be changed. A reload, a sleeping laptop and a second tab would each tell a different story.

C2

Exactly one running timer, even under a race

Two start requests arriving together would both pass an "is anything running?" check written in application code.

C3

Days that aren't 24 hours long

Sessions cross midnight, daylight-saving days are 23 or 25 hours, and the viewer's day isn't the server's.

C4

No one sees anyone else's data

One missing WHERE clause turns a task tracker into a data leak.

07

Solution

  1. S1

    A timer the browser can't get wrong

    The server owns the clock. Start and stop take no body — the server stamps both ends and computes the duration. The client anchors to the elapsed time the API reports and renders forward from it, refetching on focus, on reconnect and once a minute to catch a timer stopped on another device.

  2. S2

    Exactly one running timer, even under a race

    A partial unique index on each user's running log lets the database reject the second start — returned as a 409, its transaction rolled back. Stop is a conditional update, so two concurrent stops can't both succeed.

  3. S3

    Days that aren't 24 hours long

    A day is a half-open range in the viewer's IANA timezone, computed by PostgreSQL. Sessions are split across the days they touch, a running timer counts up to now, and a week is seven of those windows.

  4. S4

    No one sees anyone else's data

    The user id always comes from the verified session, every query is scoped by owner, someone else's task returns the same 404 as a missing one, and an authorization suite checks every task endpoint in both directions.

08

Performance

  • Analytics are aggregated in SQL, so the browser receives a small JSON summary instead of every time log.
  • Indexes are written for the queries the app actually runs.
09

Product experience

  • Dark interface with an orange accent — a sidebar on desktop, a drawer on phones — and it respects reduced-motion settings.
  • Starting a timer on a pending task moves it to in progress; completing a task stops its timer.
  • The selected week lives in the URL, so a week of work can be shared as a link.
  • The AI helper only fills in the form; you still review and save the task. With no provider configured, everything else keeps working.