A full-stack attendance management platform with biometric device integration, role-based access control, and automated attendance tracking. Built with Next.js and Express.js, containerized with Docker.
| Layer | Technology |
|---|---|
| Frontend | Next.js 16, React 19, TypeScript, Tailwind CSS 4 |
| Backend | Express.js 5, TypeScript, Prisma ORM, Zod validation |
| Database | PostgreSQL 15 |
| Auth | JWT (access + refresh tokens), bcrypt password hashing |
| Biometrics | ZKTeco device integration via zklib-js / node-zklib |
| Nodemailer (SMTP — Gmail App Passwords) | |
| API Docs | Swagger (swagger-jsdoc + swagger-ui-express) |
| Exports | ExcelJS / xlsx-js-style |
| DevOps | Docker & Docker Compose (dev + production configs) |
- Attendance Tracking — Automated check-in/check-out via biometric devices, anomaly detection, grace periods, late/undertime/overtime calculation
- Employee Management — Full employee lifecycle (ACTIVE → STAGED → INACTIVE → TERMINATED), profile pictures, contact info, employee numbers
- Shift Management — Configurable shifts with codes, break minutes, night-shift support, work days, half days, per-employee shift assignments
- Overtime Requests — Employee-submitted or manager-assigned overtime with approval workflow (PENDING → APPROVED / REJECTED)
- Attendance Adjustments — Request check-in/check-out corrections with approval workflow and audit trail
- Holiday Management — Regular and special holidays, branch-specific holiday assignments
- Organization Structure — Companies, branches, departments with many-to-many relationships
- Reports & Exports — Attendance reports with Excel export
- ZKTeco device management (connect, sync, health checks)
- Fingerprint enrollment per device per employee
- RFID card enrollment
- Device sync tasks with retry logic
- Biometric exclusion rules per device
| Role | Portal | Capabilities |
|---|---|---|
| ADMIN | /admin/dashboard |
Full system access — employees, devices, shifts, holidays, reports, logs |
| HR | /hr/dashboard |
Employee management, attendance, shifts, holidays, overtime, reports |
| MANAGER | /manager/dashboard |
Department-scoped employee & attendance management, overtime approvals |
| USER | /employee/employee |
Personal attendance, profile, overtime requests |
bits/
├── frontend/ # Next.js 16 application
│ ├── src/
│ │ ├── app/
│ │ │ ├── (admin)/ # Admin portal routes
│ │ │ ├── (auth)/ # Login page
│ │ │ ├── (employee)/ # Employee self-service portal
│ │ │ ├── hr/ # HR portal routes
│ │ │ ├── manager/ # Manager portal routes
│ │ │ └── api/ # Next.js route handlers
│ │ ├── components/ # Shared UI components (Radix UI + shadcn)
│ │ ├── context/ # React context providers
│ │ ├── features/ # Feature modules
│ │ │ ├── adjustments/ ├── attendance/
│ │ │ ├── auth/ ├── biometrics/
│ │ │ ├── dashboard/ ├── devices/
│ │ │ ├── employee-portal/ ├── employees/
│ │ │ ├── holidays/ ├── hr-portal/
│ │ │ ├── manager-portal/ ├── organization/
│ │ │ ├── overtime/ ├── reports/
│ │ │ ├── settings/ ├── shifts/
│ │ │ ├── system/ ├── system-logs/
│ │ │ └── user-accounts/
│ │ ├── hooks/ # Custom React hooks
│ │ ├── lib/ # Utility libraries
│ │ └── types/ # TypeScript type definitions
│ ├── Dockerfile
│ └── next.config.ts # API proxy rewrites to backend
│
├── backend/ # Express.js 5 API server
│ ├── src/
│ │ ├── app.ts # Express app setup
│ │ ├── index.ts # Server entry point
│ │ ├── modules/ # API route modules
│ │ │ ├── attendance/ ├── auth/
│ │ │ ├── devices/ ├── employees/
│ │ │ ├── holidays/ ├── logs/
│ │ │ ├── me/ ├── organization/
│ │ │ ├── profile-picture/ ├── reports/
│ │ │ ├── shifts/ ├── system/
│ │ │ └── users/
│ │ ├── shared/
│ │ │ ├── config/ # App configuration
│ │ │ ├── events/ # Event system
│ │ │ ├── lib/ # Shared libraries
│ │ │ ├── middleware/ # Auth, validation, rate-limiting
│ │ │ ├── services/ # Business logic services
│ │ │ ├── types/ # Shared types
│ │ │ └── utils/ # Utility functions
│ │ └── scripts/ # CLI scripts (sync, export, debug)
│ ├── prisma/
│ │ ├── schema.prisma # Database schema (18 models)
│ │ ├── seed.ts # Database seeder
│ │ └── migrations/ # Migration history
│ ├── uploads/ # Employee avatars
│ └── Dockerfile
│
├── docker-compose.yml # Development (bind mounts, hot reload)
├── docker-compose.prod.yml # Production simulation (baked images)
├── .env.local.example # Env template for local (non-Docker) development
├── .env.docker.example # Env template for Docker Compose (secrets + overrides only)
└── .gitignore
Both Docker Compose files ship with built-in ${VAR:-default} fallbacks, so the stack works out of the box without any .env file. Only set up a .env if you want to override secrets, credentials, or FRONTEND_URL.
Auto-setup on first run: When the containers start, the backend automatically runs
prisma migrate deployand seeds the database (default admin/HR accounts & config). The seed is guarded by a.seeded_lockfile so it only runs once — subsequent restarts skip it safely.
-
Choose your running mode and copy the matching env template:
-
Option A: Development Mode (Hot-Reloading) —
docker-compose.ymlUses bind mounts so code changes are reflected in real time. Copy the local template:
# Windows Command Prompt (cmd.exe): copy .env.local.example .env # PowerShell / Linux / macOS: cp .env.local.example .env
Then run:
docker-compose up --build
-
Option B: Production / Deployment Mode —
docker-compose.prod.ymlCode is baked into the container images (no bind mounts). Copy the docker template:
# Windows Command Prompt (cmd.exe): copy .env.docker.example .env # PowerShell / Linux / macOS: cp .env.docker.example .env
Then run:
docker compose -f docker-compose.prod.yml up --build
-
-
Fill in secrets (both modes):
Open the
.envyou just created and set at minimum:JWT_SECRETandJWT_REFRESH_SECRET— replace the placeholder valuesSMTP_USER/SMTP_PASS— only if you need email notifications
-
Set
FRONTEND_URLif needed:The fallback is
http://localhost:3000. Update it to match the mapped Docker host port:FRONTEND_URL=http://localhost:3013
Or your server's LAN IP for network access (e.g.
http://192.168.1.50:3013). -
Access the application:
Once the containers are running (in either mode), access the services at:
Service URL Frontend http://localhost:3013 Backend http://localhost:4013 Postgres localhost:5013
Note: The
.envfile lives at the project root. Running Prisma CLI commands directly insidebackend/will fail withEnvironment variable not found: DATABASE_URLunless you follow the steps below.
-
Copy the local env template:
# Windows Command Prompt (cmd.exe): copy .env.local.example .env # PowerShell / Linux / macOS: cp .env.local.example .env
Open
.envand updateDATABASE_URL(andDB_*fields) to match your local PostgreSQL instance:DATABASE_URL=postgresql://postgres:root@127.0.0.1:5432/db_bits
-
Install backend dependencies:
cd backend npm install -
Run database migrations:
Run from the project root so Prisma can find the root
.env:-
Option A: Run from the project root (Recommended)
npx --prefix backend prisma migrate dev --schema=backend/prisma/schema.prisma
-
Option B: Copy
.envinto the backend directory# Windows CMD: copy .env backend\.env # macOS/Linux/PowerShell: cp .env backend/.env # Then from inside backend/: npx prisma migrate dev
-
-
Seed the database & start the backend:
# From the backend/ directory: npm run seed # Seed default admin/HR accounts & configuration npm run dev:watch # Start the Express API server with hot-reload (nodemon)
Frontend:
cd frontend npm install npm run dev # Next.js dev server on port 3000 (automatically resolves root .env)
- If
npm installfails withNo matching version found for next@...: Updatefrontend/package.jsonto a valid Next.js version. - The frontend Dockerfile expects Next.js standalone build output: Ensure
next.config.tscontainsoutput: "standalone"for production builds. - If the Next.js build detects the wrong workspace root: Run build commands from inside the
frontend/folder or remove extra lockfiles. - The backend uses
patch-package: Patches inbackend/patches/are applied automatically viapostinstall. - Timezone settings: Timezone is set to
Asia/Manilain Docker containers. - Database authentication failures /
role "..." does not exist:- Cause: PostgreSQL only runs the initialization script (which sets up users and databases) on a fresh, empty volume. If you run the stack once and later change
DB_USERin.env, Postgres will skip initialization and keep the old user (e.g.root), but the backend will try to connect with the new one. - Fix: Either make sure
DB_USERin.envmatches the user the database was first created with, or delete the old docker volume and start fresh:docker volume rm project_postgres_data.
- Cause: PostgreSQL only runs the initialization script (which sets up users and databases) on a fresh, empty volume. If you run the stack once and later change
- API CORS errors (Blocked requests / Blank page on login):
- Cause: The backend CORS allowed origin must match the browser's address bar exactly.
- Fix: If accessing the app via Docker, set
FRONTEND_URL=http://localhost:3013in.env. If running locally without Docker, setFRONTEND_URL=http://localhost:3000.
- Frontend failed to proxy /
ECONNREFUSEDin Docker:- Cause: Next.js bakes path rewrites into the static routes manifest at build-time.
- Fix: The frontend Dockerfile has been configured to bake in the correct internal hostname (
ENV BACKEND_URL=http://backend:3001). If you ever modify your Docker Compose services to rename the backend service, update the build-time env in thefrontend/Dockerfileaccordingly.