A practical demonstration of the N+1 query problem in Laravel and how to solve it using eager loading. This project includes automatic lazy loading detection that logs violations without breaking your application.
This project demonstrates:
- N+1 Query Problem: How lazy loading can cause performance issues
- Eager Loading Solution: Using
with()
to optimize database queries - Lazy Loading Detection: Automatic logging of lazy loading violations
- Performance Comparison: Visual comparison using Laravel Debugbar
This project is for educational purposes and has not been fully tested for production use. The lazy loading detection code provided is a code example that could be adapted for production environments to find lazy loading issues, but it should be thoroughly tested and reviewed before being deployed to production systems.
- Product (
app/Models/Product.php
) - Products with name and description - Price (
app/Models/Price.php
) - Price history for products (50 prices per product)
- ProductsLazyController (
app/Http/Controllers/ProductsLazyController.php
) - Demonstrates N+1 problem - ProductsEagerController (
app/Http/Controllers/ProductsEagerController.php
) - Shows optimized eager loading
- 5 Products with 50 prices each (250 total price records)
- SQLite database for easy setup and portability
- AppServiceProvider (
app/Providers/AppServiceProvider.php
) - Logs lazy loading violations with detailed context
git clone <repository-url>
cd example-stop-lazy-loading
composer install -o
# Copy environment file (already configured for SQLite)
cp .env.example .env
# Generate application key
php artisan key:generate
# Create the database directory if it doesn't exist
mkdir -p database
# Create the SQLite database file
touch database/database.sqlite
# Run migrations and seed the database
php artisan migrate:fresh --seed
This creates:
- 5 products (Laptop, Smartphone, Tablet, Desktop Computer, Headphones)
- 50 price records for each product (250 total)
# Option 1: Full development environment (recommended)
composer dev
# Option 2: Just the Laravel server
php artisan serve
Open http://localhost:8000
to see two comparison buttons:
- Red Button: Products with Lazy Loading (N+1 Problem)
- Green Button: Products with Eager Loading (Optimized)
- Queries: 6 total (1 for products + 5 for each product's prices)
- Problem: Each product triggers a separate query for its prices
- Queries: 2 total (1 for products + 1 for all prices)
- Solution: All prices loaded in a single optimized query
The Laravel Debugbar appears at the bottom of each page showing:
- Total execution time
- Memory usage
- Database queries (click to see all queries)
- Query timing details
// ProductsLazyController.php
$products = Product::all(); // 1 query
foreach ($products as $product) {
$product->prices->count(); // 5 additional queries (1 per product)
}
// Total: 6 queries
// ProductsEagerController.php
$products = Product::with('prices')->get(); // 2 queries total
foreach ($products as $product) {
$product->prices->count(); // No additional queries - data already loaded
}
// Total: 2 queries
The project includes automatic lazy loading detection that:
- Monitors all Eloquent model relationships
- Logs violations without breaking the application
- Provides context for easy debugging
View lazy loading violations in:
storage/logs/laravel.log
[2025-08-29 15:12:05] local.ERROR: Lazy loading detected {"model":"App\\Models\\Product","model_id":1,"relation":"prices","file":"/home/marcovie/projects/example-stop-lazy-loading/app/Http/Controllers/ProductsLazyController.php","line":22,"url":"http://127.0.0.1:8000/products-lazy","method":"GET"}
- Model: Which model triggered the lazy loading
- Model ID: Specific model instance
- Relation: Which relationship was lazy loaded
- File & Line: Exact location in your code
- URL & Method: Request context
If you encounter the error:
Database file at path [database/database.sqlite] does not exist. Ensure this is an absolute path to the database.
Solution:
- Create the database directory:
mkdir -p database
- Create the SQLite database file:
touch database/database.sqlite
- Run migrations:
php artisan migrate:fresh --seed
If you encounter:
Failed to open stream: No such file or directory in .../vendor/autoload.php
Solution: Install dependencies first:
composer install -o
- Permission denied: Ensure the
database
directory has write permissions - Path issues: The
.env
file should haveDB_DATABASE=database/database.sqlite
(relative path from project root) - SQLite not installed: Install SQLite3 on your system if missing
.env
- SQLite database configuration.env.example
- Template with SQLite setup
app/Models/Product.php
- Product model withhasMany
prices relationshipapp/Models/Price.php
- Price model withbelongsTo
product relationship
database/migrations/*_create_products_table.php
- Products table schemadatabase/migrations/*_create_prices_table.php
- Prices table schema with foreign keydatabase/seeders/ProductSeeder.php
- Seeds 5 products with 50 prices eachdatabase/seeders/DatabaseSeeder.php
- Calls ProductSeeder
app/Http/Controllers/ProductsLazyController.php
- Demonstrates N+1 problemapp/Http/Controllers/ProductsEagerController.php
- Shows eager loading solution
routes/web.php
- Routes for both controllersresources/views/welcome.blade.php
- Homepage with comparison buttons
app/Providers/AppServiceProvider.php
- Configures lazy loading detection and logging
composer.json
- Added Laravel Debugbar for query visualizationpackage.json
- Frontend dependencies
After exploring this project, you'll understand:
- N+1 Query Problem: How innocent-looking code can cause performance issues
- Eager Loading: Using
with()
to optimize database queries - Performance Monitoring: Using Laravel Debugbar to identify bottlenecks
- Lazy Loading Detection: Implementing automatic monitoring in production
- Best Practices: Writing efficient Eloquent queries
The lazy loading detection is already configured in AppServiceProvider.php
. Important: This code is provided as an educational example and should be thoroughly tested before production use.
For production consideration:
- Test thoroughly in staging environments first
- Monitor logs regularly for lazy loading violations
- Fix violations by adding appropriate
with()
clauses - Consider alerting for critical performance issues
- Review performance impact of the detection mechanism itself
- Use Laravel Debugbar in development
- Consider tools like Laravel Telescope for production debugging
- Monitor database query counts and execution times
- Always test detection code in non-production environments first
Feel free to submit issues and pull requests to improve this educational example!
This project is open source and available under the MIT License.