Case Study
ApprovalFlow — Internal Approval Workflow Built with Laravel and Livewire
ApprovalFlow is a compact internal workflow application built with Laravel 13, Livewire 4 and Flux UI. This case study focuses on how a simple approval lifecycle was modeled, how business rules were encapsulated inside the domain layer, and how a clean server-driven interface was used to create a realistic back-office style experience.
Overview
ApprovalFlow was designed as a compact internal-tool project focused on a simple but realistic approval lifecycle. The application allows authenticated users to create approval requests, review them in a structured list and process them through explicit states such as pending, approved and rejected. Although the scope was intentionally small, the project was built around patterns commonly used in real internal workflow systems.
The main objective was to build a portfolio-ready project that could demonstrate more than basic CRUD operations. For that reason, the application was structured around domain rules, clear state transitions, dashboard visibility and automated tests protecting the most important workflow behaviors.
System Architecture
Domain Modeling
The core domain is centered around a single entity: ApprovalRequest. Each request stores its title, optional description, workflow status and decision metadata such as who created it, who approved or rejected it, and when that decision happened. This kept the model small while still providing enough structure to behave like a real internal process.
Livewire-Based Interface
Livewire 4 was used to build the create form, the filtered request listing and the dashboard. The goal was to keep the application server-driven while still offering a responsive interface with search, status filters, quick actions and live feedback. This approach kept the stack simple and cohesive without introducing the overhead of a separate SPA frontend.
Workflow State Layer
Instead of treating status changes as raw database updates, workflow actions were encapsulated inside the model through dedicated methods such as approve() and reject(). This made the behavior easier to reason about and ensured that transitions update related fields consistently, including timestamps and decision ownership.
Dashboard and Visibility
A lightweight dashboard was added to summarize the workflow at a glance. Instead of relying on a purely decorative landing page, the dashboard provides useful metrics such as total requests, pending count, approved count and rejected count, along with a snapshot of recent activity. This reinforces the project’s internal-tool orientation and makes the product feel more operational.
Key principle: keep the user flow simple on the surface, while making workflow transitions explicit, traceable and protected by clear business rules underneath.
Technical Decisions
- Explicit workflow states instead of generic flags: the project uses clear statuses such as pending, approved and rejected, which makes both the UI and the domain logic easier to understand and maintain.
- Model-driven state transitions: approval and rejection are handled through dedicated model methods rather than ad-hoc updates in the UI layer, improving consistency and making the business behavior easier to test.
- Server-driven UI with Livewire: instead of splitting the project into a Laravel API and a separate frontend, Livewire was used to keep development fast while still delivering dynamic interactions such as filters, search and action buttons.
- Business rule testing with Pest: the project includes tests covering request creation, approval, rejection and invalid state transitions, helping verify that the domain behaves consistently beyond the visual interface.
Approval Workflow
The approval workflow was designed to reflect a simple internal process. A user creates a request with a title and optional description, and the system stores it with a default pending status. From the request index, the item can then be reviewed and moved into an approved or rejected state.
When a request is approved, the system records the decision timestamp and the user responsible for the action. The same principle applies to rejections. By storing this metadata directly on the model, the application keeps the workflow traceable without introducing unnecessary complexity such as a separate audit table for the MVP.
One of the most important design decisions was to prevent invalid transitions once a request is no longer pending. This means the workflow behaves less like a loose CRUD interface and more like a controlled process, where actions have meaning and state changes are intentional.
Lessons Learned
Building this project reinforced the importance of modeling behavior, not just data. Even in a small application, explicit domain methods and clear state rules make the system easier to reason about, easier to test and easier to extend. It also highlighted how much value a lightweight dashboard can add when presenting operational workflows.
Another key takeaway was that a portfolio project becomes much stronger when it includes protected business rules and automated tests. The project was useful not only as a Livewire interface exercise, but also as practice in thinking like a product engineer: defining states, transitions, ownership and traceability from the beginning.
Future Improvements
Future iterations could introduce role-based permissions, a dedicated decision note interface, activity history per request and notification flows for status changes. It would also make sense to expand the reporting layer with more metrics, refine authorization rules for different reviewer roles and add deeper Livewire-level tests for interface interactions.