A production-ready, full-stack task management application with role-based access control (RBAC), category organization, admin analytics dashboards, and a clean, dynamic user experience. Built with React 19, Express, TypeScript, PostgreSQL, and Prisma ORM.
- Overview
- Architecture
- Tech Stack
- Folder Structure
- Prerequisites
- Quick Start (Docker)
- Local Development
- Environment Variables
- Database Migrations
- Seeding
- Default Credentials
- API Endpoints
- Development Workflow
- Troubleshooting
Task Tracker Lite lets users create, organize, and track tasks across categories with priorities, statuses, and due dates. An admin role provides a system-wide analytics dashboard, user management, and advanced task filtering. The repository is fully containerized and designed to run with a single docker compose up --build after copying an environment template.
The application follows a clean layered architecture on the backend and a feature-based structure on the frontend.
┌────────────┐ HTTP/JSON ┌─────────────────┐ Prisma ┌────────────┐
│ Frontend │ ───────────────▶ │ Backend API │ ───────────────▶ │ PostgreSQL │
│ (React + │ ◀─────────────── │ (Express + TS) │ ◀─────────────── │ │
│ Vite) │ localhost:5000 │ │ db:5432 (Docker)│ │
└────────────┘ └─────────────────┘ └────────────┘
Backend layers (request flow):
Route → Validation middleware → Controller → Service → Repository → Prisma → PostgreSQL
↘ AppError → Error handler → JSON error response
- Routes define endpoints and attach auth/authorization + validation middleware.
- Controllers parse the request and shape HTTP responses.
- Services contain business logic and authorization rules.
- Repositories encapsulate Prisma database access.
- Middleware handles authentication (JWT), RBAC authorization, Zod validation, and centralized error handling.
Frontend flow: React Router (layout-based nested routes) → TanStack Query (data fetching/caching) → Axios (API client with JWT interceptor) → React Hook Form + Zod (forms/validation).
| Layer | Technology |
|---|---|
| Frontend | React 19, Vite, TypeScript, Tailwind CSS, React Router v7, TanStack Query, React Hook Form, Zod, Axios |
| Backend | Node.js, Express, TypeScript, Prisma ORM, Zod, Helmet, CORS, Morgan, Winston, express-rate-limit |
| Database | PostgreSQL 16 |
| Auth | JWT (Bearer tokens), bcrypt password hashing, RBAC (ADMIN / USER) |
| Tooling | ESLint, Prettier, ts-node-dev, Docker, Docker Compose, nginx |
task-tracker-lite/
├── docker-compose.yml # Orchestrates db, api, frontend
├── .env.example # Root env template (consumed by compose)
├── README.md
├── backend/
│ ├── Dockerfile # Multi-stage build + startup (migrate, seed, serve)
│ ├── package.json
│ ├── tsconfig.json # Main TS config
│ ├── tsconfig.seed.json # Compiles prisma/seed.ts → dist/seed.js
│ ├── prisma/
│ │ ├── schema.prisma # Data model
│ │ ├── seed.ts # Idempotent seed (admin, users, categories, tasks)
│ │ └── migrations/ # Prisma migration history
│ └── src/
│ ├── app.ts # Express app (middleware, routes)
│ ├── server.ts # HTTP server + graceful shutdown
│ ├── config/ # env, logger, prisma client
│ ├── routes/ # auth, tasks, categories, admin, health
│ ├── controllers/ # Request handlers
│ ├── services/ # Business logic
│ ├── repositories/ # Data access (Prisma)
│ ├── middleware/ # auth, validation, error handler
│ ├── validations/ # Zod schemas
│ ├── utils/ # jwt, appError, asyncHandler
│ └── types/ # Express type augmentations
└── frontend/
├── Dockerfile # Multi-stage build (Vite) + nginx serve
├── nginx.conf # SPA fallback + /api reverse proxy
├── vite.config.ts
└── src/
├── api/ # Axios instance + interceptors
├── components/ # admin, tasks, categories, common
├── context/ # AuthContext
├── hooks/ # useTasks, useCategories, useAdmin
├── layouts/ # Auth + Dashboard layouts
├── pages/ # auth, dashboard, tasks, categories, admin, profile
├── routes/ # ProtectedRoute, AdminRoute, AppRoutes
├── services/ # API service modules
├── types/ # Shared TS types
└── validations/ # Zod form schemas
- Docker and Docker Compose (Docker Compose v2 recommended) — for the containerized setup.
- Node.js v20+ and npm — for local development without Docker.
- PostgreSQL 16 (running locally or in a container) — only required for local (non-Docker) development.
The fastest way to get the entire stack running.
# 1. Clone
git clone https://github.com/bhumikabiyani/task-tracker-lite.git
cd task-tracker-lite
# 2. Configure environment
cp .env.example .env
# (optional) edit .env to set a strong JWT_SECRET and POSTGRES_PASSWORD
# 3. Build and start all services
docker compose up --build
# In the background:
docker compose up --build -dThen visit:
| Service | URL |
|---|---|
| Frontend UI | http://localhost:3000 |
| Backend API | http://localhost:5000/api |
| Health check | http://localhost:5000/api/health |
On startup the backend automatically runs migrations, the seed, and then starts the server — no manual DB setup required. Sign in with the default credentials.
Stop & reset everything (including the database volume):
docker compose down -vNote: If you use the legacy v1 binary, substitute
docker-compose(with a hyphen) fordocker compose.
Run the frontend and backend directly with Node.js (useful for hot-reload iteration).
Start a PostgreSQL instance (the Docker DB is convenient):
# Start only the database via Docker
docker compose up -d db
# It exposes PostgreSQL on localhost:5432Or use any local PostgreSQL 16 instance. Create a database named task_tracker.
cd backend
cp .env.example .env # edit DATABASE_URL + JWT_SECRET for your local DB
npm install
npx prisma generate # generate the Prisma Client
npx prisma migrate dev # apply migrations to the local DB
npm run db:seed # seed demo data (idempotent)
npm run dev # start dev server with hot reloadThe backend runs at http://localhost:5000/api.
cd frontend
cp .env.example .env
npm install
npm run dev # Vite dev serverThe frontend runs at http://localhost:3000 and expects the backend at http://localhost:5000/api (configurable via VITE_API_BASE_URL).
Three environment files are used. Never commit real .env files — only .env.example templates are tracked.
| Variable | Description | Default |
|---|---|---|
POSTGRES_USER |
PostgreSQL superuser | postgres |
POSTGRES_PASSWORD |
PostgreSQL password | change_me_to_a_strong_password |
POSTGRES_DB |
Database name | task_tracker |
DB_PORT |
Host port for PostgreSQL | 5432 |
NODE_ENV |
Node environment | production |
PORT / API_PORT |
Backend container/host port | 5000 |
JWT_SECRET |
JWT signing secret (≥12 chars, 32+ recommended) | — required |
JWT_EXPIRES_IN |
Token lifetime | 1d |
CORS_ORIGIN |
Comma-separated allowed origins | http://localhost:3000 |
LOG_LEVEL |
error | warn | info | http | debug |
info |
FRONTEND_PORT |
Host port for the frontend | 3000 |
VITE_API_BASE_URL |
API URL the browser uses | http://localhost:5000/api |
VITE_APP_NAME |
App display name | Task Tracker Lite |
The backend's
DATABASE_URLis constructed by Compose fromPOSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DBwith the hostdb(the service name), so credentials never drift between the database and the API.
| Variable | Description |
|---|---|
DATABASE_URL |
Local Prisma connection string, e.g. postgresql://postgres:postgres@localhost:5432/task_tracker?schema=public |
JWT_SECRET |
JWT signing secret (≥12 chars) |
JWT_EXPIRES_IN |
Token lifetime |
CORS_ORIGIN |
Allowed origin(s); * is fine for local dev |
LOG_LEVEL |
Logging level |
PORT / NODE_ENV |
5000 / development |
| Variable | Description |
|---|---|
VITE_API_BASE_URL |
Backend API base URL (no trailing slash) |
VITE_APP_NAME |
App display name |
VITE_NODE_ENV |
Frontend environment tag |
Generate a strong JWT secret:
openssl rand -base64 32Migrations live in backend/prisma/migrations and are applied automatically:
- Docker:
prisma migrate deployruns in the container entrypoint before the server starts. - Local dev: run
npx prisma migrate devto apply migrations and create new ones as the schema evolves.
After changing schema.prisma, generate a migration locally:
cd backend
npx prisma migrate dev --name <descriptive_name>The seed (backend/prisma/seed.ts) is idempotent — it uses upsert / find-or-create, so it is safe to run repeatedly without destroying user-created data.
- Docker: runs automatically on every container start via
node dist/seed.js(the seed is compiled during the image build so it needs nots-nodein production). - Local dev:
npm run db:seed(equivalent tonpx prisma db seed, which runs the TypeScript source viats-node).
The seed creates: 1 Admin + 2 standard Users, 3 Categories, and 5 sample Tasks, and prints the credentials to the console.
Use these to sign in immediately after seeding:
| Role | Password | Purpose | |
|---|---|---|---|
| Admin | admin@example.com |
Password123 |
Full access: analytics dashboard, user management, all tasks |
| Standard User | alice@example.com |
Password123 |
Standard task lists, search, categorization |
| Standard User | bob@example.com |
Password123 |
Task assignment and progress states |
⚠️ These are demo credentials for local evaluation only. Change them (or remove the seed) before any real deployment.
All request bodies and responses are JSON. Protected routes require Authorization: Bearer <JWT>.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/auth/register |
Public | Register a new standard user. Body: { firstName, lastName, email, password } |
POST |
/api/auth/login |
Public | Authenticate and receive a JWT. Body: { email, password } |
GET |
/api/auth/me |
Bearer | Get the current authenticated user |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/categories |
Bearer | List categories |
GET |
/api/categories/:id |
Bearer | Get a category |
POST |
/api/categories |
Admin | Create. Body: { name, description?, color? } |
PATCH |
/api/categories/:id |
Admin | Update a category |
DELETE |
/api/categories/:id |
Admin | Soft-delete a category |
Regular users see tasks they created or are assigned to; admins see all tasks.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/tasks |
Bearer | Paginated, filtered, sorted task list |
GET |
/api/tasks/:id |
Bearer | Get task details |
POST |
/api/tasks |
Bearer | Create a task. Body: { title, dueDate, status?, priority?, categoryId, description?, assignedTo? } |
PUT |
/api/tasks/:id |
Bearer | Update a task. Body: any subset of the create fields |
DELETE |
/api/tasks/:id |
Bearer | Delete a task |
Task list query params: page, limit, status, priority, categoryId, search, sortBy, sortOrder
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/admin/dashboard |
System-wide statistics |
GET |
/api/admin/tasks |
All tasks with advanced filters |
GET |
/api/admin/users |
All users with task metrics (paginated, searchable) |
GET |
/api/admin/users/:id/tasks |
Tasks created by / assigned to a user |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/health |
Public | Server + database health |
# Backend (from /backend)
npm run dev # start with hot reload
npm run build # compile to dist/ (tsc + tsc-alias)
npm run build:seed # compile prisma/seed.ts → dist/seed.js
npm run lint # eslint
npm run lint:fix # eslint --fix
npm run format # prettier
# Frontend (from /frontend)
npm run dev # Vite dev server
npm run build # production build
npm run preview # preview the production buildTypical change loop:
- Edit
backend/prisma/schema.prisma. npx prisma migrate dev --name <change>(applies + generates migration).npx prisma generate.- Implement the feature across repository → service → controller → route.
npm run lintandnpm run buildto verify.- Commit the new migration SQL along with the code.
You may have the legacy v1 binary. Use docker-compose (hyphen) instead, or install Docker Compose v2.
The root .env is missing or JWT_SECRET is empty. Run cp .env.example .env and set a value of at least 12 characters.
- Docker: ensure the
dbservice is healthy (docker compose ps). The backend uses hostdb, notlocalhost. - Local dev: confirm
DATABASE_URLinbackend/.envpoints to your reachable PostgreSQL instance onlocalhost:5432.
The seed may not have run. In Docker it runs automatically on start; locally run npm run db:seed from the backend directory. Verify the printed credentials.
Another process (or a previous container) is holding the port. Either stop it or remap the host port via .env (FRONTEND_PORT, API_PORT, DB_PORT).
The nginx healthcheck pings 127.0.0.1:80. If unhealthy, rebuild the image: docker compose up --build frontend.
PostgreSQL stores credentials in the data volume on first init. Change POSTGRES_PASSWORD, then recreate the volume: docker compose down -v && docker compose up --build.
docker compose down -v # removes containers + the database volume
docker compose up --buildThis project is provided as-is for educational and evaluation purposes.