LCHR HR Platform
An internal HR platform built for a ~30-person team, architected toward supporting 100+ users — unifying people management, performance tracking, and project reporting in a single system.
Core developer — wrote a large share of the attendance and leave features and closed access-control gaps before production
~30-person internal org
Jan 2026 – Aug 2026
Next.js · Node.js/Express · PostgreSQL · Redis · Docker
Internal Company Project — Learning Crew This is a proprietary internal platform built for Learning Crew. Source code and live deployment links are not publicly available. All details are shared for portfolio purposes only.
Overview
LCHR (Learning Crew HR) is an internal HR and operations platform originally built by a founder/technical lead as an MVP for a team of about 30 people. I joined its development after the MVP stage as one of its core developers — writing a large share of its attendance and leave features and leading its access-control hardening — working alongside the original builder, who stayed involved, and a frontend-focused engineer. Together we matured it into a considerably more complete system covering three core pillars:
- People & HR — Employee management, attendance, leave, payroll, expenses
- Performance — OKR/KPI tracking, 1:1 meetings, review cycles
- Projects & internal communication — Project/task tracking, weekly reports, company notices and documents
It was used internally by the team throughout my time on it.
Built as a unified monolith with a layered architecture (API → business logic → database), the system was designed for operational simplicity without sacrificing structure — architected with a target of 100+ users in mind, well ahead of the org's actual ~30-person size at the time.
The Problem
As the team grew, the tools it had started with stopped fitting together:
- Attendance, leave, and approvals lived in spreadsheets and manual steps
- HR, performance, and project updates were spread across separate third-party tools with no integration
- There was no single source of truth for employee data, leave balances, or OKR progress
- Related processes depend on each other — leave affects payroll, OKRs feed into reviews — but the data didn't connect
Bringing these into one internal system, with business rules enforced in code rather than by hand, was the natural next step.
My Role
The platform's founder/technical lead built the initial MVP for internal use and stayed closely involved after I joined. From that point, I was one of its core developers, with sustained responsibility for attendance and leave, access-control hardening, and day-to-day maintenance.
I worked remotely from India with the team in Korea on requirements, code review, deployment, and fixes, with working hours overlapping their office and a written update to the team at the end of every working day.
I worked closely with a frontend-focused engineer throughout. A typical pattern: they'd build a frontend feature and share it for review; I'd assess what backend support it needed, implement or wire up that logic, and merge the combined work into the shared branch. On a number of features, they also implemented backend logic directly — this was shared, ongoing work, and I'm one of its primary contributors rather than its sole author.
What I Owned
- Leave management — one of the areas I worked on most consistently: accrual, balance auditing, and approvals (see Key Features for scope).
- Attendance processing — clock-in/out session logic, break-time rules, night-work approvals, and related business rules.
- Public holiday data — integrating Korean public holiday data into attendance and leave calculations.
- Company notice board — the internal announcement system with view tracking and threaded comments.
- Admin and management workflows — employee administration, user approval/invitation flows, and permission management.
- Export and reporting — Excel-based exports for attendance and payroll data.
- Localization — Korean/English translation across frontend and backend.
- Security and data-integrity work — see Selected Challenges & Solutions below for specifics.
- Database and caching performance — indexing, query-pattern fixes, and cache-invalidation correctness.
- Database scripts and migrations — schema changes, data backfills, and feature-specific seed scripts.
- Environment and deployment configuration — environment-variable setup and build/deploy configuration for both services.
- Ongoing maintenance — investigating and fixing issues as they surfaced during regular internal use, alongside planned feature work.
Tech Stack
Next.js 14 (App Router) · React 18 · TypeScript 5 · Tailwind CSS · Material-UI · TanStack Query v5 · React Hook Form + Yup · next-intl · Recharts · FullCalendar
Node.js · Express.js · TypeScript 5 · Sequelize + sequelize-typescript · express-validator · Helmet · csurf · bcryptjs · jsonwebtoken · transactional email provider · Handlebars · ExcelJS · Winston · prom-client
PostgreSQL
Redis — refresh tokens · cache layer with graceful fallback
Docker · Docker Compose · a managed PaaS
Architecture
Client — Next.js 14
App Router · locale-based [locale] routes · React Query for server state · current-user hook for route protection & permission-aware nav
API Layer — Express
Helmet → CORS → compression → body parsing → CSRF protection → JWT auth → permission checks → controller
Services
attendance · leave · payroll · expenses · objectives · projects · notices
Redis
refresh tokens · cache layer (permissions, work-hours config) · graceful fallback
Scheduled Jobs (cron)
leave accrual · night-work processing · employee termination effective-dating
External Integrations
transactional email provider · public holiday API
Database — PostgreSQL
Sequelize ORM · ~60 domain models
Key decisions:
- Modular, versioned REST API with a route module per domain (attendance, leave, payroll, expenses, objectives, projects, reports, documents, administration)
- RBAC with permission slugs — roles map to fine-grained
resource:actionpermissions, enforced in backend middleware and mirrored by a permission-gate component on the frontend so the UI only offers what the user can actually do - Scalable service-layer pattern — Clear separation between controllers (HTTP), services (business logic), and models (data access)
- Background processing — cron-based schedulers handled leave accrual, night-work processing, and employee-termination effective-dating outside the request/response cycle
Key Features
- Authentication & Authorization — JWT access/refresh tokens (refresh tokens stored server-side in Redis, scoped per session with a TTL), role- and permission-based access control. Users could view and revoke their own active login sessions individually or all at once; each session recorded the browser/user-agent and IP address it was created from — visibility into active sessions, not device fingerprinting.
- People Management — Employee CRUD, org directory, onboarding checklists/templates.
- Attendance — Clock in/out with work-mode selection, break tracking and policy enforcement, night-work approval and auto clock-out, monthly attendance close, audit trail.
- Leave Management — Leave requests (annual, partial-day, compensatory, with reason capture), approval workflow, hour-based balance tracking with legal/manual accrual split, batch grants, discrepancy auditing; accrual rules aligned to Korean labor conventions for employees under one year of tenure.
- Payroll — Payroll records, localized payslip generation (Korean + English templates), payment status tracking.
- Expense Management — Submission and approval workflow, card-transaction import, status history.
- Performance / OKR — Objectives and key results, progress updates, monthly check-ins, key-result history, review cycles.
- 1:1 Meetings — Scheduling and listing, tied to manager/employee relationships.
- Projects & Tasks — Projects, epics, tasks, kickoff/meeting tracking.
- Weekly Reports — Company/team/member scopes, filtering, comment threads.
- Notices & Documents — Internal announcement system with view-count tracking and threaded comments, plus a company document library and employment/career certificate generation.
- Admin & Management — User registration approval, invite-token onboarding, employee administration, role/permission management, audit logging, leave grant history.
- Export & Reporting — Excel exports for attendance (a raw session-level export and a processed export with computed daily overtime) and payroll Excel import/export.
- Internationalization — Korean and English throughout: backend error codes, frontend strings, payslip templates, date/time formats.
- Security Hardening — CORS validation, rate limiting on authentication endpoints, CSRF protection (double-submit pattern), and field-level encryption for sensitive personal identifiers.
- External Integrations — A transactional email provider and a public holiday-data API were functioning integrations used by the backend.
Selected Challenges & Solutions
Data Integrity in Attendance and Leave Processing
Several attendance and leave operations required multiple sequential database writes — for example, closing out a work session while updating a derived leave balance. Without transaction boundaries, a failure partway through (a crashed request, a concurrent edit) could leave data in a partially-updated, inconsistent state with no clean way to detect or roll it back — a real risk given that these records directly affect pay and time-off entitlements.
Wrapped these multi-step write paths in the attendance and leave services in database transactions, so each operation fully commits or fully rolls back, and added concurrency constraints on attendance records to prevent overlapping sessions from being written simultaneously. This was proactive hardening based on recognizing the risk in the existing write patterns, not a response to an observed data-corruption incident.
Record-Level Authorization Before Production
Role checks answer "can this user perform this kind of action?" — they don't answer "can this user act on this specific record?" Before production use, I ran a self-directed review of record-level authorization (the IDOR class of issue) across several internal APIs and closed the access-control gaps it surfaced.
The fix was structural rather than per-endpoint patching: ownership and scope checks moved into the service layer, so a record is only returned or modified if it belongs to the requesting user or falls within their permitted scope (for example, a manager's own team). A dedicated test suite covers access-scoping behavior so the guarantee holds as new endpoints are added.
Consistent Identity Handling Across Services
Several features distinguished between a user's account ID and their linked employee-record ID, and that distinction wasn't applied consistently as the feature set grew. Two concrete symptoms: OKR key-result creation returned an authorization error for users without a linked employee record, because that endpoint required an employee ID while the parent objective endpoint already tolerated a user ID; and 1:1 meeting creation failed outright because the frontend sent the user ID where the backend and its listing query expected the employee ID.
OKR Key-Result Create
Required employeeId · authorization error for users without one
1:1 Meeting Create
Sent user.id · backend expected employeeId
Resolved Actor ID
employeeId ?? userId
Ownership Check
ownership permitted → proceed, else clear validation error
Aligned both flows to consistently fall back from the employee ID to the user ID (employeeId ?? userId) wherever ownership already permitted it, and added a clear validation error instead of a silent failure when a required employee link was genuinely missing — a cross-cutting consistency bug that surfaces naturally as a system with multiple contributors and overlapping identity models grows.
Masked Sensitive-ID Input Breaking in Hidden Mode
A masked input for a sensitive ID number stopped accepting input after two digits when the eye icon was toggled off. The form was re-parsing the masked string (containing *) and stripping non-digits, collapsing the stored value. Fixed by computing the delta (characters added or removed) against the current masked length and applying it to the stored full digit string — append new digits or trim from the end. Unmasked mode keeps the existing format-from-visible-value path.
Smaller Fixes Worth Noting
A number of smaller, self-contained fixes rounded out day-to-day maintenance: an express-validator quirk where optional() didn't accept explicit null values, which silently blocked saving weekly reports with any empty field; a client/server clock-skew bug that made the attendance timer briefly show an incorrect elapsed time right after clock-in; a Korean vs. Western name-ordering inconsistency across the app, resolved with a shared, locale-aware formatName function; and a Redis cache-invalidation path that used a blocking key-pattern scan (KEYS) instead of the non-blocking cursor-based SCAN — a proactive improvement made ahead of growth, not a response to an observed production stall.
Impact
- Centralized operations — Attendance, leave, payroll, and expenses moved from spreadsheets and disconnected third-party tools into a single internal system, alongside performance management and project/work reporting.
- Helped mature the founder's MVP — Built on an early internal MVP, over sustained ongoing work, to reach a considerably more complete platform covering the team's day-to-day HR and internal-management needs end-to-end.
- Expanded admin and management capability — Employee administration, user approval/invitation flows, and role/permission management.
- Improved correctness across real workflows — Fixes to leave balance calculation, cross-service identity handling (OKR/1:1), masked sensitive-ID input, and attendance timing addressed concrete gaps between intended and actual behavior.
- Strengthened security posture — CSRF protection, field-level encryption for sensitive identifiers, and access-control gaps in several internal APIs closed before production use — proactive engineering work, not incident response.
- Architected toward future scale — Connection pooling, indexing, pagination, N+1 elimination, and cache-invalidation correctness were implemented with a 100+-user engineering target in mind, well ahead of the org's actual ~30-person size at the time. At that actual usage level, I didn't encounter recurring concurrency-related issues (not a formal load-test result).
- Delivered Korean/English localization — Across backend error codes, frontend strings, payslip templates, and date/time handling.
- Sustained the platform through ongoing maintenance — Regularly investigating and fixing issues as they surfaced during real internal use, alongside planned feature work.
- 400+ commits across this platform and the automotive dealer platform combined.
Scope & confidentiality: This case study reflects my time on the project — not the company's current product, team, or production status. I collaborated closely with the platform's founder/technical lead (who built the original MVP and remained involved) and a frontend-focused engineer; the work described here is shared, ongoing development rather than solely my individual output. No formal load-test results or external security audit were available, so performance and security claims are described in terms of implementation, not measured outcomes.