First Onw HR
ProductionModern full-stack employee management platform — Laravel 12, PHP 8, MySQL, Blade & Eloquent, re-engineered stateless-first for Vercel serverless PHP on AWS Lambda.
Overview
First Onw HR is a complete, authenticated employee management platform: registration, login, email verification, password reset, profile management, and a paginated employee directory with full CRUD flows. Build on a modern Laravel 12 + MySQL full-stack, then re-engineered stateless-first so it runs flawlessly on Vercel's serverless PHP runtime. The engineering thesis is not the CRUD — it is the deployment substrate. Every framework default that assumed a persistent filesystem was deliberately re-bound.
Problem Statement
Laravel expects a persistent, writable filesystem — storage/ for compiled views, sessions, cache and logs; bootstrap/cache for configuration; and a long-lived web server process to boot the framework. A stateless function runtime provides none of these: the filesystem is read-only after deploy, /tmp is ephemeral and instance-local, and every invocation may boot cold.
Architecture & Tech Stack
A bespoke api/index.php front controller pre-declares the storage override, idempotently materializes the writable tree under /tmp, then rebinds storage via Application::useStoragePath() before Laravel boots. All state moves out of the filesystem: SESSION_DRIVER, CACHE_STORE and QUEUE_CONNECTION run on the shared Aiven MySQL instance, converting stateless functions into a stateful application. Blade views and Eloquent models stay idiomatic Laravel while the hybrid Vercel build graph (framework: null) keeps the PHP lambda and the CDN-served static edge on one domain.
Challenges
- ▸Read-only filesystem after deploy — Laravel's first eager-loaded compiled view threw file_put_contents errors until the storage path was re-bound to /tmp
- ▸Ephemeral /tmp breaks sessions — file-backed sessions silently 'forget' users, so all state moved to database drivers
- ▸Cold-start latency — dev-only providers suppressed via dont-discover, optimized autoloader, and committed Vite assets served from the edge
- ▸TLS terminated at the CDN — URL::forceScheme('https') in production to avoid mixed-content and broken redirects
Key Metrics
25
Records per page — simplePaginate(25)
4
Employee CRUD ops over 6 fields
5
Feature tests on the full lifecycle + auth guards
17
Commits incl. an explicit serverless-hardening phase