I originally developed the E-Library Management System as a university project, but I built it as a practical showcase for Pragmatic Clean Architecture in Laravel rather than a basic CRUD app.
My goal was to keep development fast while making the codebase maintainable long-term, and to make sure it holds up against common security issues and race conditions under concurrent load.

📸 User Interface (UI/UX)
I designed the system with a playful but professional theme, keeping the UI optimized for library staff who use it daily.
I set up a surface hierarchy for light and dark environments so contrast and readability stay consistent either way.
Light Mode Palette:
- Background: Soft Slate Tint (
#F8FAFC) with Crisp White Cards (#FFFFFF) - Primary: Bubblegum Pink (
#EC4899) - Secondary: Sky Blue (
#38BDF8) - Success Accent: Mint Green (
#34D399)
Dark Mode Palette:
- Background: Deep Slate (
#0F172A) with Elevated Panels (#1E293B) - Primary: Soft Pink (
#F472B6) - Secondary: Soft Blue (
#60A5FA) - Success Accent: Mint Green (
#34D399)
For typography, I chose Nunito Sans across both modes. It's rounded and readable, which fits the academic context well.

🚀 Core Features
I built the application to cover the full library workflow:
- Monitoring Dashboard: A dashboard showing key stats (overdue books, active borrow requests) with quick access to daily operational menus.
- Book & Category Management: I added support for book cover uploads with automatic cleanup, strict MIME type validation, and live stock tracking (total vs. available stock).
- Borrowing Circulation Flow: A borrowing system that supports multiple items per transaction, tied to specific member IDs.
- Flexible Returns & Dynamic Fines: I implemented partial returns (returning a fraction of borrowed items without resolving the whole ticket). Overdue fines are recalculated on each request based on daily system configurations.
- Automated Waitlist & Reservation System: When a borrowed book is
returned, the circulation transaction checks for pending waitlists, locks the
reservation record, and updates the next member's status to
readywith a strict 48-hour expiration window.

🤖 Vibe Coding
This was the first project where I tried out "Vibe Coding" (AI-assisted development). Even though I let AI do a lot of the typing, I made sure not to skip the architecture and design work.
Before I wrote any code, I spent some time thinking about the architecture and
edge cases. I documented the plan in the repository's .ai/ directory:
- Requirement Analysis: Figuring out the edge cases in circulation logic so I don't get headaches later.
- Security Threat Modeling: Designing defenses against double-returns and race conditions before writing any code.
- Data Flow Design: Structuring database relationships and locking mechanisms.
AI helped me write code faster, but I still needed to understand the design before I started.
🏛️ Architecture & Code Principles
I applied my favorite "Thin Controllers, Fat Actions" philosophy to avoid spaghetti code in large MVC apps.
- Thin Controllers: Controllers only receive HTTP Requests, delegate
validation to
FormRequestclasses, and invoke the requiredActionclass. - Isolated Action Classes: All complex business logic lives in Action
classes (like
BorrowBooksActionorReturnBorrowingItemAction), making circulation logic reusable and easy to test in isolation. I prevent Double Returns by intercepting invalid states and throwing aDomainExceptionbefore ever touching the database. - Dashboard Query Services: Dashboard metrics (return ratios, financial
trends) are calculated with raw SQL
CASE WHENaggregations inside dedicated service classes (e.g.,DashboardQueryService). This avoids loading full Eloquent collections into memory just to count things. - Dumb Blade Views: I built the presentation layer using dumb Blade templates combined with Tailwind CSS. No database queries or business logic in the presentation layer.
- Dynamic Accessors: I calculate overdue days and fine amounts on the fly
using Laravel Model Accessors (
getLateDaysAttribute) rather than storing integers that can go stale in the database.
Database-Level Protection (Pessimistic Locking)
The trickiest part was preventing negative stock caused by race conditions. I handled it using database transactions and pessimistic locking:
// Example implementation within an Action Class
DB::transaction(function () use ($bookIds) {
// lockForUpdate() prevents other requests from modifying these rows until my transaction completes
$books = Book::whereIn('id', $bookIds)->lockForUpdate()->get();
foreach ($books as $book) {
if ($book->stock_available < 1) {
throw new Exception("Stock for {$book->title} is empty.");
}
$book->decrement('stock_available');
}
});
🔒 Security Posture & Hardening
Security was part of the design from the start, not added at the end. I covered the basics (preventing IDOR and privilege escalation) without over-engineering it:
- Mass Assignment Protection: I always use
$request->validated()fromFormRequestinstances so no unexpected fields can mutate my database. - Insecure File Upload Prevention: Extensions and MIME types are validated. Files are stored with random hash names to prevent execution exploits, and orphaned files are cleaned up via Laravel's Storage API.
- Destructive Action Guards: Deletion endpoints (
destroy()) check relationship constraints (e.g.,$member->borrowings()->exists()) and return a clear error rather than exposing raw SQL exceptions to the user. - XSS & Enumeration Defense: Blade output is escaped by default, and I
sanitize all wildcard search parameters (
%and_) to prevent Slow Query Enumeration attacks. - HTTP Security Headers: Middleware enforces
X-Frame-Options,X-Content-Type-Options, andReferrer-Policy.
🧪 Testing Strategy
I wrote tests for every main library flow using Pest PHP:

My test suite covers:
- Feature Tests: All CRUD flows work as expected.
- Business Logic Tests: Fine calculations are correct and stock can never drop below zero, regardless of the scenario.
- Security Regression: Endpoints protected by role (Admin vs. Staff) can't be accessed by the wrong role.

