A modern, full-stack note management application built with Laravel 12 and Livewire 3.6
This application has been developed with Claude Code (Sonnet 4.5)
Future security improvements are contained in SECURITY_REPORT.md file
NoteLoom is a professional demonstration application showcasing advanced proficiency in Laravel and modern frontend frameworks. Built as a technical showcase, it implements a complete note management system with real-time reactive components, intelligent database-driven search and pagination, and a polished, responsive user interface.
This application was developed as a solution to a technical assessment challenge with the following constraints:
- Tech stack: Laravel 12 + Livewire 3.6 (no additional packages allowed)
- Goal: Demonstrate best practices in modern full-stack development
- Focus areas: Code quality, Laravel conventions, reactive UI patterns, and clean architecture
Repository: https://github.com/jlmue/noteloom
- Features
- Requirements Met
- Prerequisites
- Quick Start
- Manual Installation
- Demo Account
- Running the Application
- Usage Guide
- Application Architecture
- Database Schema
- Development
- Testing
- Troubleshooting
- Tech Stack
-
Complete CRUD Operations
- Create new notes with title, content, and importance flag
- Read and display all notes with pagination
- Update existing notes (inline editing)
- Delete notes with confirmation
-
Smart Search
- Real-time search across note titles and content
- Database-driven search with optimized queries
- Search results update instantly as you type
- Preserves pagination state during search
-
Advanced Filtering
- Filter by importance (show only important notes)
- Combined search and filter capabilities
- Instant filter application with Livewire reactivity
-
Importance Marking
- Visual distinction for important notes (gold star icons)
- Quick toggle importance status
- Filter view to show only important notes
-
Responsive Design
- Mobile-first approach with Tailwind CSS
- Adaptive layouts for all screen sizes
- Touch-friendly interfaces
- Optimized for phones, tablets, and desktops
-
Real-time Pagination
- Efficient database pagination
- Smooth page transitions
- Maintains filter and search state across pages
- Shows total results and current page info
-
Authentication System
- Secure user login
- Password validation and encryption
- Session management
- Protected routes
-
Intuitive Dashboard
- Clean, modern interface
- Easy-to-use note creation button
- Grid layout for optimal note viewing
- Visual feedback for all actions
-
Loading States
- Livewire loading indicators
- Smooth transitions between states
- Progress feedback for async operations
-
Data Persistence
- Automatic state management
- No data loss during navigation
- Reliable database handling
-
Service Layer Architecture
- Separation of concerns with dedicated service classes
NoteSearchServicefor search logic- Reusable business logic
- Clean, testable code
-
Optimized Database Queries
- Efficient search with indexed columns
- Pagination to limit query results
- Query optimization for performance
-
Event-Driven Communication
- Livewire event system for component communication
- Reactive state management
- Real-time UI updates
-
Code Quality
- PSR-12 compliant code style (Laravel Pint)
- Comprehensive comments and documentation
- Follows Laravel best practices and conventions
- Clean, maintainable code structure
This application fulfills all core requirements and includes several bonus features:
| Requirement | Status | Implementation |
|---|---|---|
| Display all notes | ✅ Complete | Dashboard with paginated note list |
| Create new notes | ✅ Complete | Form with validation |
| Edit existing notes | ✅ Complete | Inline editing with save/cancel |
| Delete notes | ✅ Complete | Delete with confirmation dialog |
| Responsive design | ✅ Complete | Mobile-first Tailwind CSS design |
| Note: title field | ✅ Complete | String field with validation |
| Note: content field | ✅ Complete | Text field with validation |
| Note: created_at | ✅ Complete | Automatic timestamps |
| Note: is_important | ✅ Complete | Boolean field with toggle |
| Bonus Feature | Status | Implementation Details |
|---|---|---|
| Search functionality | ✅ Complete | Real-time search in title and content |
| Filter functionality | ✅ Complete | Filter by importance status |
| Sorting | ✅ Complete | Sort by creation date (newest first) |
| Tests | ✅ Complete | Pest feature tests included |
- Service Layer Pattern: Clean architecture with
NoteSearchService - Demo Data Seeder: Realistic example data with
NoteSeeder - Comprehensive Documentation: README.md and inline comments
- Loading States: User feedback during async operations
- Error Handling: Graceful error messages and validation feedback
Before installing NoteLoom, ensure your system meets these requirements:
- PHP: 8.2 or higher
- Composer: Latest version
- Node.js: v24.11.0
- NPM: v11.6.1
- Database: SQLite (default & recommended for quick setup) or MySQL/PostgreSQL
Check your versions:
php -v
composer -V
node -v
npm -vThe fastest way to get NoteLoom up and running:
git clone https://github.com/jlmue/noteloom.git
cd noteloomRun the automated setup script:
composer setupThis command will:
- ✅ Install Composer dependencies
- ✅ Create
.envfile from.env.example - ✅ Generate application key
- ✅ Run database migrations
- ✅ Install NPM dependencies
- ✅ Build frontend assets
To populate the application with 100 example notes and a demo user:
php artisan db:seedcomposer devThis starts all development services concurrently:
- 🌐 PHP development server → http://localhost:8000
- 🔄 Queue worker
- 📋 Log viewer (Pail)
- ⚡ Vite dev server with Hot Module Replacement
Open your browser and navigate to:
http://localhost:8000
If you prefer step-by-step installation:
# Install PHP dependencies
composer install
# Install JavaScript dependencies
npm install# Copy environment file
cp .env.example .env
# Generate application key
php artisan key:generateUsing SQLite (Default):
The database file is automatically created. Simply run migrations:
php artisan migrateUsing MySQL/PostgreSQL:
Update your .env file with database credentials:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=noteloom
DB_USERNAME=your_username
DB_PASSWORD=your_passwordThen run migrations:
php artisan migratephp artisan db:seedDevelopment build:
npm run devProduction build:
npm run buildOption A: All services (Recommended)
composer devOption B: Individual services
# Terminal 1: PHP server
php artisan serve
# Terminal 2: Queue worker
php artisan queue:listen
# Terminal 3: Vite dev server
npm run dev
# Terminal 4: Log viewer
php artisan pailAfter running php artisan db:seed, you can log in with the demo account:
Email: demo@noteloom.com
Password: yFvvYxs!RYPR1fY2gNu&%Wy#LwFpicik
The seeder creates:
- ✅ 1 demo user account
- ✅ 100 realistic example notes with varied content
- ✅ Mix of important and regular notes
- ✅ Timestamps spanning the last 3 months
All services (recommended):
composer devAccess at: http://localhost:8000
Stop all services: Press Ctrl+C in the terminal
# Development server only
php artisan serve
# Vite dev server with HMR
npm run dev
# Queue worker
php artisan queue:listen
# Log viewer
php artisan pail# Build optimized assets
npm run build
# Serve with optimized assets
php artisan serveThis section provides a comprehensive walkthrough of using NoteLoom after installation.
-
Access the Application
- Open your browser to
http://localhost:8000 - You'll be redirected to the login page
- Open your browser to
-
Option A: Use Demo Account
Email: demo@noteloom.com Password: yFvvYxs!RYPR1fY2gNu&%Wy#LwFpicik(Only available if you ran
php artisan db:seed)
After logging in, you'll see the main dashboard with:
- Header: Application name and user menu (logout)
- Search Bar: Real-time search across all your notes
- Filters: Toggle to show only important notes
- Create Note Button: Blue button to add new notes
- Notes Grid: Your notes displayed in a responsive grid
- Pagination Controls: Navigate through pages if you have many notes
-
Open Create Form
- Click the "Create Note" button (blue button with plus icon)
- Create Page appears
-
Fill Note Details
- Title: Enter a descriptive title (required)
- Content: Write your note content (required)
- Mark as Important: Check the checkbox to flag as important
-
Save the Note
- Click "Create Note" button to save
- You are automatically navigated back to the dashboard
- Your new note appears in the grid
- Notes are sorted by creation date (newest first)
Validation:
- Title and content are required fields
- You'll see red error messages if fields are empty
- Form won't submit until validation passes
Grid Layout:
- Notes are displayed in a responsive grid
- Each note card shows:
- Title (bold, larger font)
- Content preview (truncated if long)
- Creation date (e.g. "04.11.2025")
- Updated at (In human-readable format)
- Importance icon (gold star if marked important)
- Action buttons (Edit, Delete)
Pagination:
- 6 notes per page by default
- Use "Previous" and "Next" buttons to navigate
- Current page and total results shown
- Pagination maintains search and filter state
-
Enter Edit Mode
- Click the "Edit" button (pencil icon) on any note card or the notes title
- The note card expands to edit mode
-
Modify Note Details
- Update the title in the text input
- Update the content in the textarea
- Toggle the "Important" checkbox to change importance
-
Save or Cancel
- Save: Click the blue "Save" button to persist changes
- Cancel: Click the gray "Cancel" button to discard changes
- Changes are immediately reflected without page reload
Note: You can only edit one note at a time. Starting to edit another note will cancel the current edit.
-
Click Delete Button
- Click the "Delete" button (trash icon) on any note card
- A browser confirmation dialog appears
-
Confirm Deletion
- Click "OK" to permanently delete the note
- Click "Cancel" to keep the note
-
Note is Removed
- The note disappears from the grid immediately
- No undo is available - deletion is permanent
Tip: Be careful with deletions, especially if you have important information!
Real-Time Search:
- Type in the search box at the top of the dashboard
- Search works across both title and content
- Results update instantly as you type
- Search is case-insensitive
How It Works:
- Searches for your query in note titles AND content
- Uses database-level search for performance
- Pagination resets to page 1 when searching
- Shows total matching results
Examples:
- Search "meeting" - finds all notes with "meeting" in title or content
- Search "2025" - finds notes mentioning that year
- Search partial words - "proj" finds "project", "projection", etc.
Clear Search:
- Delete text from search box to show all notes again
- Or click any clear/reset button if available
Filter by Importance:
- Click the "Important Only" filter toggle/checkbox
- Only notes marked as important are displayed
- Filter works in combination with search
- Pagination adjusts to filtered results
Combining Search and Filter:
- You can use search and filter together
- Example: Search "meeting" + Filter "Important" = Important notes about meetings
- Both filters are preserved when navigating pages
Clear Filter:
- Uncheck the "Important Only" filter
- All notes (or search results) will be shown again
Mark Note as Important:
- Edit the note
- Check the "Mark as important" checkbox
- Save the note
- The note now displays a gold star icon
Remove Importance:
- Edit the note
- Uncheck the "Mark as important" checkbox
- Save the note
- The gold star icon is removed
Visual Indicators:
- Important notes have a gold/yellow star icon
- Icon appears near the note title
- Makes important notes easy to spot at a glance
NoteLoom is fully responsive and works great on mobile devices with at least 320px (can be improved):
Phone Layout:
- Notes stack in a single column
- Touch-friendly buttons and forms
- Forms adapt to screen size
- Easy scrolling and navigation
Tablet Layout:
- Notes display in 2 columns
- Comfortable touch targets
- Optimal use of screen space
Tips for Mobile:
- Use the search bar to quickly find notes
- Swipe to scroll through long notes
- Tap edit/delete buttons carefully to avoid mistakes
- Portrait orientation works best for editing
While NoteLoom doesn't have dedicated keyboard shortcuts, you can use standard browser shortcuts:
- Tab: Navigate between form fields
- Enter: Submit forms (when focused on input)
- Escape: Close modals (browser default)
- Ctrl/Cmd + F: Browser's find function (different from app search)
Organization:
- Use descriptive titles for easy scanning
- Mark truly important notes only (avoid over-flagging)
- Use the search function to find old notes
- Review and delete outdated notes regularly
Content:
- Keep note content focused and concise
- Use line breaks for readability
- Include dates or deadlines in content if relevant
- Consider using keywords for better searchability
Performance:
- The app handles thousands of notes efficiently
- Search is optimized for speed
- Pagination keeps the interface responsive
- No lag even with large note collections
Issue: Can't create a note
- Check that title and content are not empty
- Look for red validation error messages
- Ensure you're logged in
- Try refreshing the page
Issue: Search not working
- Check that you're typing in the search box (not browser find)
- Wait a moment for results to load
- Try clearing the search and searching again
- Refresh the page if search seems stuck
Issue: Note won't save when editing
- Ensure you clicked the blue "Save" button
- Check for validation errors
- Verify you're still logged in
- Try canceling and editing again
Issue: Can't see my notes
- Check if a filter is active (deactivate "Important Only")
- Clear the search box
- Verify you created notes while logged in as this user
- Try refreshing the page
NoteLoom follows Laravel best practices and modern architectural patterns for clean, maintainable code.
The application uses Laravel's MVC architecture enhanced with Livewire components:
User Request → Routes → Livewire Component → Service Layer → Model → Database
↓
Blade View (with Livewire directives)
app/
├── Livewire/
│ ├── Dashboard.php # Main dashboard component (note list)
│ ├── NoteForm.php # Create/edit note
│ └── LoginForm.php # Authentication
├── Models/
│ ├── Note.php # Note Eloquent model
│ └── User.php # User model (with authentication)
├── Services/
│ └── NoteSearchService.php # Search & filter business logic
└── Providers/
└── AppServiceProvider.php # Service container bindings
resources/views/
├── livewire/
│ ├── dashboard.blade.php # Dashboard view
│ ├── note-form.blade.php # Note form
│ └── login-form.blade.php # Login view
└── components/
└── layout.blade.php # Base layout
database/
├── migrations/
│ └── 2024_xx_xx_create_notes_table.php
├── factories/
│ └── NoteFactory.php # Test data factory
└── seeders/
└── NoteSeeder.php # Demo data seeder
Responsibilities:
- Display paginated list of notes
- Handle search and filter state
- Manage note editing mode
- Delete notes with confirmation
- Emit events to child components
Key Features:
- Reactive properties:
$search,$importantOnly,$editingNoteId - Computed property:
#[Computed] notes()for pagination - Event listeners: Listens for
note-createdandnote-updated - Real-time reactivity with Livewire's
wire:model
Responsibilities:
- Create new notes via form
- Validate form input
- Emit success events to parent
Key Features:
- Form validation with Laravel's validation rules
- Livewire
#[Validate]attributes - Event dispatch on successful creation
- Automatic form reset after save
Responsibilities:
- Centralized search and filter logic
- Build optimized database queries
- Separate business logic from presentation
Key Features:
- Accepts search term and importance filter
- Returns query builder for flexible pagination
- Efficient LIKE queries with OR conditions
- Adheres to Single Responsibility Principle
- User clicks "Create Note" button in
Dashboard - Form in
NoteFormcomponent opens - User fills title, content, importance flag
- Form validation runs on submit
NoteFormsaves note to database viaNotemodelNoteFormemitsnote-createdeventDashboardlistens to event and refreshes note list- UI updates automatically via Livewire reactivity
- User types in search box (bound with
wire:model.live) - Livewire detects change and calls
updated()hook inDashboard Dashboardresets pagination to page 1notes()computed property re-executesNoteSearchServicebuilds filtered query- Database returns matching notes
- Blade view re-renders with updated results
- No page reload required
The application implements a service layer to separate business logic from presentation:
Benefits:
- Testability: Services can be unit tested independently
- Reusability: Same logic can be used in multiple components
- Maintainability: Changes to search logic only affect one class
- Single Responsibility: Components focus on UI, services focus on logic
Example: NoteSearchService
// app/Services/NoteSearchService.php
public function search(User $user, ?string $search = null, ?bool $importantOnly = null)
{
$query = Note::where('user_id', $user->id);
if ($search) {
$query->where(function ($q) use ($search) {
$q->where('title', 'like', "%{$search}%")
->orWhere('content', 'like', "%{$search}%");
});
}
if ($importantOnly) {
$query->where('is_important', true);
}
return $query->orderBy('created_at', 'desc');
}Livewire Properties:
- All component state is stored in public properties
- Properties are automatically synced between frontend and backend
wire:model.liveprovides real-time two-way binding
Session State:
- User authentication uses Laravel's session-based auth
- Database-backed sessions for persistence
Database State:
- All notes are persisted to SQLite database
- Eloquent ORM handles all database interactions
- Migrations ensure consistent schema
Components communicate via Livewire's event system:
Dispatching Events:
$this->dispatch('note-created'); // From NoteFormListening to Events:
#[On('note-created')]
public function refreshNotes() { /* ... */ }Benefits:
- Loose coupling between components
- Components don't need direct references to each other
- Easy to add new listeners without modifying dispatchers
- Authentication: All note routes protected by
authmiddleware - Authorization: Users can only see/edit their own notes
- CSRF Protection: All forms include CSRF tokens automatically
- SQL Injection Prevention: Eloquent uses parameter binding
- XSS Prevention: Blade escapes output by default
- Mass Assignment Protection: Models use
$fillablewhitelist
The core notes table stores all user notes:
CREATE TABLE notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
is_important BOOLEAN DEFAULT 0,
created_at TIMESTAMP,
updated_at TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);Column Details:
| Column | Type | Nullable | Default | Description |
|---|---|---|---|---|
id |
INTEGER | No | Auto | Primary key |
user_id |
INTEGER | No | - | Foreign key to users table |
title |
VARCHAR(255) | No | - | Note title/heading |
content |
TEXT | No | - | Full note content |
is_important |
BOOLEAN | Yes | false | Importance flag |
created_at |
TIMESTAMP | Yes | NULL | Creation timestamp |
updated_at |
TIMESTAMP | Yes | NULL | Last update timestamp |
Indexes:
- Primary key on
id(automatic)
Relationships:
user_id→users.id(Many-to-One)- Cascade delete: Deleting a user deletes all their notes
Standard Laravel authentication table:
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
email_verified_at TIMESTAMP NULL,
password VARCHAR(255) NOT NULL,
remember_token VARCHAR(100) NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP
);Cache Table (cache)
- Stores database cache entries
- Used for application-level caching
Sessions Table (sessions)
- Stores user session data
- Enables database-backed sessions
Jobs Table (jobs)
- Stores queued jobs
- Used for background processing (if needed)
┌─────────────────┐
│ users │
├─────────────────┤
│ id (PK) │
│ name │
│ email │
│ password │
│ created_at │
│ updated_at │
└────────┬────────┘
│
│ 1:N
│
▼
┌─────────────────┐
│ notes │
├─────────────────┤
│ id (PK) │
│ user_id (FK) │◄──── Foreign Key
│ title │
│ content │
│ is_important │
│ created_at │
│ updated_at │
└─────────────────┘
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Note extends Model
{
protected $fillable = [
'title',
'content',
'is_important',
];
protected $casts = [
'is_important' => 'boolean',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}Key Features:
- Mass Assignment Protection:
$fillablearray whitelists allowed fields - Type Casting:
is_importantautomatically cast to boolean - Relationship:
user()method defines belongsTo relationship - Timestamps: Automatically managed by Laravel
Common Queries Used:
// Get all user's notes with pagination
Note::where('user_id', $userId)
->orderBy('created_at', 'desc')
->paginate(10);
// Search notes
Note::where('user_id', $userId)
->where(function($q) use ($search) {
$q->where('title', 'like', "%{$search}%")
->orWhere('content', 'like', "%{$search}%");
})
->orderBy('created_at', 'desc')
->paginate(10);
// Filter important notes
Note::where('user_id', $userId)
->where('is_important', true)
->orderBy('created_at', 'desc')
->paginate(10);Format code with Laravel Pint:
# Fix code style
vendor/bin/pint
# Check without fixing
vendor/bin/pint --test# Fresh migration (drops all tables)
php artisan migrate:fresh
# Fresh migration with seeding
php artisan migrate:fresh --seed
# Rollback last migration
php artisan migrate:rollback
# Run specific seeder
php artisan db:seed --class=NoteSeeder# Clear all caches
php artisan optimize:clear
# Clear specific caches
php artisan config:clear
php artisan route:clear
php artisan view:clear# Create new Livewire component
php artisan make:livewire ComponentName
# View list of Livewire components
php artisan livewire:listNoteLoom uses Pest for testing.
# Using composer script
composer test
# Or directly
php artisan test# Run specific file
php artisan test tests/Feature/ExampleTest.php
# Run specific test
php artisan test --filter=test_example
# With parallel execution
php artisan test --parallelTests use an in-memory SQLite database for speed. Configuration is in tests/Pest.php.
Solution:
# Fix storage and cache permissions
chmod -R 775 storage bootstrap/cacheSolution: Redis is not required. Update .env:
QUEUE_CONNECTION=database
CACHE_STORE=database
SESSION_DRIVER=databaseSolution:
# Remove node_modules and reinstall
rm -rf node_modules package-lock.json
npm installSolution for SQLite:
# Ensure database file exists
touch database/database.sqlite
php artisan migrateSolution for MySQL:
- Verify credentials in
.env - Ensure database exists
- Check MySQL service is running
Solution:
# Build assets
npm run buildSolution:
# Use different port
php artisan serve --port=8080If you encounter issues:
- Check Laravel logs:
storage/logs/laravel.log - Enable debug mode: Set
APP_DEBUG=truein.env - Clear caches: Run
php artisan optimize:clear - Verify environment: Run
php artisan about
- Laravel 12 - PHP framework
- Livewire 3.6 - Full-stack reactive framework
- PHP 8.2+ - Programming language
- SQLite - Default database (MySQL/PostgreSQL supported)
- Pest - Testing framework
- Tailwind CSS 4.0 - Utility-first CSS framework
- Vite - Frontend build tool
- Alpine.js - Included with Livewire for JavaScript interactions
- Laravel Pint - Code formatter
- Laravel Pail - Log viewer
- Concurrently - Run multiple dev servers
- Hot Module Replacement - Instant frontend updates during development
.
├── app/
│ ├── Livewire/ # Livewire components
│ ├── Models/ # Eloquent models
│ └── ...
├── database/
│ ├── factories/ # Model factories
│ ├── migrations/ # Database migrations
│ └── seeders/ # Database seeders
├── resources/
│ ├── css/ # Stylesheets
│ ├── js/ # JavaScript
│ └── views/ # Blade templates
├── routes/
│ └── web.php # Web routes
├── tests/ # Pest tests
└── ...
Key environment variables in .env:
APP_NAME=NoteLoom
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost:8000
DB_CONNECTION=sqlite
# DB_CONNECTION=mysql # Alternative
QUEUE_CONNECTION=database
CACHE_STORE=database
SESSION_DRIVER=databaseThis project is open-sourced software licensed under the MIT license.
Built with ❤️ using Laravel and Livewire
