A full-stack monorepo scaffold for the HR Inventory workforce management system.
| Layer | Stack | Port |
|---|---|---|
| Database | PostgreSQL 15 (via Docker) | 5432 |
| Backend | Express · TypeScript · ts-node-dev · JWT · WebSocket (ws) |
4000 |
| Frontend | Vite · React 18 · TypeScript · Tailwind CSS · React Query | 5173 |
- Prerequisites
- Project Structure
- Quick Start (Run Both Together)
- Step-by-Step Setup
- Available Scripts
- Environment Variables Reference
- Architecture Overview
Make sure the following tools are installed on your machine before proceeding:
| Tool | Minimum Version | Check Command |
|---|---|---|
| Node.js | 18+ | node -v |
| npm | 9+ | npm -v |
| Docker | 24+ | docker -v |
| Docker Compose | v2+ | docker compose version |
hrinventory/
├── backend/ # Express + TypeScript API server
│ ├── src/
│ │ ├── config/ # App config (port, DB URL, JWT secret)
│ │ ├── database/ # DB connection utilities
│ │ ├── middleware/ # Request logger, error handler
│ │ ├── utils/ # Shared helpers
│ │ └── server.ts # Entry point
│ ├── modules/ # Feature modules (auth, users, attendance)
│ │ ├── auth/
│ │ ├── users/
│ │ └── attendance/
│ ├── database/
│ │ ├── migrations/ # SQL migration files
│ │ └── seed/ # Seed data scripts
│ ├── websocket/ # WebSocket server logic
│ ├── docker-compose.yml # PostgreSQL 15 service definition
│ ├── .env.example # Backend env template
│ └── package.json
│
├── frontend/ # Vite + React + Tailwind SPA
│ ├── src/
│ │ ├── App.tsx # Root component & routing shell
│ │ ├── router.tsx # React Router definitions
│ │ ├── AuthContext.tsx # Global auth state (JWT)
│ │ ├── axios.ts # Pre-configured Axios instance
│ │ ├── LoginPage.tsx
│ │ ├── ProfilePage.tsx
│ │ ├── ProfileForm.tsx
│ │ ├── Layout.tsx
│ │ ├── ProtectedRoute.tsx
│ │ ├── lib/ # Shared utilities / hooks
│ │ └── styles/ # Global CSS / Tailwind base
│ ├── .env.example # Frontend env template
│ ├── vite.config.ts
│ └── package.json
│
└── README.md
Recommended approach: open three terminal tabs — one for each service.
cd backend
docker compose up -d
sudo systemctl stop postgresql (Optional: if postgresql already exist)cd backend
npm install # first time only
cp .env.example .env # first time only — then edit .env
npm run devcd frontend
npm install # first time only
cp .env.example .env # first time only
npm run devOnce all three services are running:
| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| Backend | http://localhost:4000 |
| API Health Check | http://localhost:4000/health |
# Clone the repository
git clone <your-repo-url> hrinventory
cd hrinventory
# Install backend dependencies
cd backend && npm install && cd ..
# Install frontend dependencies
cd frontend && npm install && cd ..cd backend
cp .env.example .envOpen backend/.env and fill in the values:
PORT=4000
NODE_ENV=development
# For local Docker Compose, use 'localhost' instead of 'db' for the host
DATABASE_URL=postgres://postgres:postgres@localhost:5432/hrinventory
JWT_SECRET=replace_this_with_a_strong_secret
# Optional OAuth providers (leave blank to skip)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
AUTH0_DOMAIN=
AUTH0_CLIENT_ID=Important: When running the backend outside Docker (i.e., directly with
npm run dev), uselocalhostas the DB host instead ofdb.
cd frontend
cp .env.example .envfrontend/.env only needs one variable — it should already be correct:
VITE_API_URL=http://localhost:4000The database runs as a Docker container. Start it from the backend/ directory:
cd backend
docker compose up -dVerify PostgreSQL is running:
docker compose ps
# You should see 'db' container with status: UpTo stop PostgreSQL:
docker compose down # stop containers (data preserved)
docker compose down -v # stop + destroy data volumecd backend
npm run devThe backend starts with ts-node-dev (hot-reload on file changes). You should see:
Backend listening on http://localhost:4000
Verify it works:
curl http://localhost:4000/health
# → { "status": "ok" }cd frontend
npm run devVite will start the dev server and print:
VITE v5.x.x ready in Xms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
Open http://localhost:5173 in your browser.
| Command | Description |
|---|---|
npm run dev |
Start dev server with hot-reload (ts-node-dev) |
npm run build |
Compile TypeScript to dist/ |
npm start |
Run compiled production build (dist/server.js) |
npm run lint |
Run ESLint on all .ts files |
npm run format |
Format codebase with Prettier |
| Command | Description |
|---|---|
npm run dev |
Start Vite dev server (HMR enabled) |
npm run build |
Build production bundle to dist/ |
npm run preview |
Serve the production build locally |
npm run lint |
Run ESLint on .ts / .tsx files |
npm run format |
Format codebase with Prettier |
| Command | Description |
|---|---|
docker compose up -d |
Start PostgreSQL in background |
docker compose down |
Stop PostgreSQL (data preserved) |
docker compose down -v |
Stop PostgreSQL and destroy volume (wipe DB) |
docker compose logs -f db |
Follow PostgreSQL logs |
| Variable | Default | Required | Description |
|---|---|---|---|
PORT |
4000 |
No | Port the Express server listens on |
NODE_ENV |
development |
No | Node environment |
DATABASE_URL |
— | Yes | PostgreSQL connection string |
JWT_SECRET |
CHANGE_ME |
Yes | Secret for signing JWT tokens |
GOOGLE_CLIENT_ID |
— | No | Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
— | No | Google OAuth client secret |
AUTH0_DOMAIN |
— | No | Auth0 domain |
AUTH0_CLIENT_ID |
— | No | Auth0 client ID |
| Variable | Default | Required | Description |
|---|---|---|---|
VITE_API_URL |
http://localhost:4000 |
Yes | Base URL of the backend API |
All frontend env vars must be prefixed with
VITE_to be accessible in the browser.
Browser
│
▼
Vite Dev Server (localhost:5173)
│ HTTP/REST via Axios
│ (VITE_API_URL → localhost:4000)
▼
Express API (localhost:4000)
│ ├─ GET /health
│ ├─ /auth routes (JWT, Google OAuth, Auth0)
│ ├─ /users routes
│ ├─ /attendance routes
│ └─ WebSocket upgrade (ws)
│
▼
PostgreSQL 15 (localhost:5432)
└─ Docker volume: db_data
| File/Dir | Role |
|---|---|
src/server.ts |
App bootstrap — Express setup, middleware, HTTP server |
src/config/index.ts |
Reads .env and exports typed config object |
src/middleware/request.logger |
Logs every incoming HTTP request |
src/middleware/error.middleware |
Centralized error handling |
modules/auth/ |
Auth routes, JWT helpers, OAuth strategies |
modules/users/ |
User CRUD |
modules/attendance/ |
Attendance records |
websocket/ |
WebSocket server for real-time features |
database/migrations/ |
Raw SQL migration scripts |
database/seed/ |
Seed data for development |
| File | Role |
|---|---|
src/main.tsx |
React entry point — mounts <App /> to the DOM |
src/App.tsx |
Root component with QueryClientProvider |
src/router.tsx |
All route definitions (React Router) |
src/AuthContext.tsx |
Global auth state (token storage, user info) |
src/axios.ts |
Axios instance pre-configured with VITE_API_URL |
src/ProtectedRoute.tsx |
HOC that redirects unauthenticated users to login |
src/LoginPage.tsx |
Login form UI |
src/ProfilePage.tsx |
User profile view |
| Problem | Solution |
|---|---|
ECONNREFUSED on backend startup |
Database isn't running — run docker compose up -d first |
DATABASE_URL host db not resolving |
Change db → localhost in backend/.env when running outside Docker |
| Frontend shows blank page | Check VITE_API_URL in frontend/.env points to http://localhost:4000 |
Port 4000 already in use |
Kill the conflicting process: lsof -ti:4000 | xargs kill |
Port 5173 already in use |
Kill the conflicting process: lsof -ti:5173 | xargs kill |
JWT_SECRET default warning |
Set a strong secret in backend/.env — never commit real secrets |