A robust REST API built with Laravel 12 for managing a product catalog with advanced search capabilities, caching, and cloud storage integration.
- Features
- Requirements
- Tech Stack
- Quick Start
- API Endpoints
- Running Tests
- API Testing
- Technical Decisions
- Known Limitations
- Next Steps
- Production Readiness
- ✅ Complete CRUD operations for products
- ✅ MySQL database with soft deletes
- ✅ ElasticSearch integration for advanced search and filtering
- ✅ Redis caching with intelligent invalidation
- ✅ AWS S3 integration for product image uploads
- ✅ Docker environment (app + MySQL + Redis + ElasticSearch)
- ✅ Comprehensive test suite (Unit + Feature tests)
- ✅ GitHub Actions CI/CD pipeline
- ✅ Clean architecture with Repository and Service patterns
- ✅ Automatic ElasticSearch synchronization via Observers
Minimum Requirements:
- Docker 20.10+
- Docker Compose 2.0+
- Git
Optional (for local development without Docker):
- PHP 8.2+
- Composer 2.0+
- MySQL 8.0+
- Redis 7+
- ElasticSearch 8.11+
| Component | Technology | Version |
|---|---|---|
| Framework | Laravel | 12.x |
| Language | PHP | 8.2+ |
| Database | MySQL | 8.0 |
| Cache | Redis | 7.x |
| Search Engine | ElasticSearch | 8.11 |
| Storage | AWS S3 | - |
| Web Server | Nginx | Alpine |
| Testing | PHPUnit | 11.x |
| CI/CD | GitHub Actions | - |
git clone <repository-url>
cd catalog-apiThe .env file is already configured for Docker. For AWS S3, update:
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_BUCKET=catalog-products
AWS_DEFAULT_REGION=us-east-1Note: S3 is optional. The app will fallback to local storage if AWS credentials are not configured.
docker-compose up -dWait for services to be ready (~30 seconds)
docker-compose exec app composer installdocker-compose exec app php artisan migratedocker-compose exec app php artisan db:seeddocker-compose exec app php artisan elasticsearch:create-index
docker-compose exec app php artisan elasticsearch:reindexcurl http://localhost:8000/api/productsExpected response: {"data":[],"meta":{...}}
./setup.shThis script will:
- ✅ Start Docker containers
- ✅ Install dependencies
- ✅ Run migrations
- ✅ Seed database
- ✅ Create ElasticSearch index
- ✅ Verify installation
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/api/products |
List all products (paginated) | No |
GET |
/api/products/{id} |
Get product by ID | No |
POST |
/api/products |
Create new product | No |
PUT |
/api/products/{id} |
Update product | No |
DELETE |
/api/products/{id} |
Delete product (soft delete) | No |
POST |
/api/products/{id}/image |
Upload product image to S3 | No |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/api/search/products |
Search products with filters | No |
Pagination:
page(default: 1)per_page(default: 15, max: 100)
Filters:
category(string)status(active/inactive)min_price(decimal)max_price(decimal)
Sorting:
sort(price, created_at, name)order(asc, desc)
Search:
q(search query)
docker-compose exec app php artisan testOr using Makefile:
make test# Unit tests only
make test-unit
# Feature tests only
make test-feature
# Specific test
make test-filter FILTER=test_can_create_product
# Verbose output
make test-verboseWhat's Tested:
- ✅ Product CRUD operations
- ✅ SKU uniqueness validation
- ✅ Cache behavior (hit/miss/invalidation)
- ✅ Search with filters (ElasticSearch)
- ✅ Image upload (S3 + local fallback)
- ✅ Error handling (404, 422, 400)
- ✅ Soft deletes
- ✅ Pagination
- ✅ Repository pattern
- ✅ Service layer
PASS Tests\Unit\ProductServiceTest
✓ can create product
✓ can update product
✓ can delete product
PASS Tests\Feature\ProductTest
✓ can create product
✓ cannot create product with duplicate sku
✓ can update product
✓ can delete product
✓ show nonexistent product returns 404
✓ product is cached after first request
✓ cache is cleared when product is updated
PASS Tests\Feature\ProductSearchTest
✓ can search products
✓ can filter by category
✓ can filter by price range
Tests: 15 passed (25 assertions)
Duration: 2.34s
-
Import Collection
# Open Postman # Click "Import" → "Upload Files" # Select: postman_collection.json
-
Set Environment
- The collection includes a
baseUrlvariable - Default:
http://localhost:8000/api
- The collection includes a
-
Run Requests
- Expand folders: Products, Filters, Search, Error Cases
- Click any request and hit "Send"
Download: https://www.postman.com/downloads/
- Import Collection
# Open Insomnia # Click "Import/Export" → "Import Data" → "From File" # Select: insomnia_collection.json
Download: https://insomnia.rest/download
- Install Extension: "REST Client" by Huachao Mao
- Open:
api-requests.http - Click: "Send Request" above each request
# Create Product
curl -X POST http://localhost:8000/api/products \
-H "Content-Type: application/json" \
-d '{
"sku": "LAPTOP-001",
"name": "Gaming Laptop",
"price": 1299.99,
"category": "Electronics"
}'
# Get All Products
curl http://localhost:8000/api/products?per_page=10&page=1
# Search Products
curl "http://localhost:8000/api/search/products?q=laptop&category=Electronics"Total: 19 pre-configured requests
- Products (6): Create, List, Get, Update, Delete, Upload Image
- Filters (5): Category, Status, Price Range, Sort by Price, Sort by Date
- Search (5): Simple, Multiple Filters, Pagination, Category Only, Complex
- Error Cases (3): Validation Error, Duplicate SKU, Not Found
1. Repository Pattern
- Why: Separates data access logic from business logic
- Benefit: Easy to swap data sources (MySQL → MongoDB)
- Location:
app/Repositories/ProductRepository.php
2. Service Layer
- Why: Encapsulates business rules and orchestrates operations
- Benefit: Controllers stay thin, logic is reusable
- Location:
app/Services/ProductService.php
3. Observer Pattern
- Why: Automatic ElasticSearch sync on model events
- Benefit: No manual sync needed, always up-to-date
- Location:
app/Observers/ProductObserver.php
4. Form Request Validation
- Why: Centralized validation logic
- Benefit: Reusable, testable, clean controllers
- Location:
app/Http/Requests/
Redis Cache with Smart Invalidation:
// Cache individual products (120s TTL)
Cache::remember("product.{$id}", 120, fn() => Product::find($id));
// Cache product lists (120s TTL)
Cache::remember("products.page.{$page}", 120, fn() => Product::paginate());
// No cache for pages > 50 (avoid memory bloat)
if ($page > 50) {
return Product::paginate($perPage);
}
// Invalidate on changes
Cache::forget("product.{$id}");
Cache::flush(); // For list cachesWhy this approach:
- ✅ Fast response times (cache hit ~5ms vs DB query ~50ms)
- ✅ Automatic invalidation on updates
- ✅ Memory-efficient (no deep pagination cache)
Current: Synchronous via Observer
// ProductObserver.php
public function created(Product $product) {
$this->elasticSearchService->indexProduct($product);
}Why synchronous:
- ✅ Simpler implementation
- ✅ Immediate consistency
- ✅ No queue infrastructure needed
Trade-off:
- ❌ Slight delay on create/update (~50ms)
- ✅ Mitigated: Queue job available but not enabled by default
Why soft deletes:
- ✅ Data recovery possible
- ✅ Audit trail maintained
- ✅ Referential integrity preserved
Implementation:
// Product.php
use SoftDeletes;
// Automatically adds deleted_at column
// DELETE /api/products/1 → sets deleted_at = now()Fallback Strategy:
// ImageUploadService.php
try {
$path = Storage::disk('s3')->put('products', $image);
} catch (\Exception $e) {
// Fallback to local storage
$path = Storage::disk('public')->put('products', $image);
}Why fallback:
- ✅ Works without AWS credentials (development)
- ✅ Graceful degradation
- ✅ No deployment blockers
Current State:
- Synchronous via Observer
- ~50ms delay on product create/update
Mitigation:
- ✅ Queue job available (
SyncProductToElasticSearch) - ✅ Can be enabled by uncommenting in
ProductObserver
Impact: Minimal for typical workloads (<100 products/min)
Future Solution:
// Enable queue-based sync
dispatch(new SyncProductToElasticSearch($product));Current State:
- Uses
Cache::flush()for list caches - Clears ALL product-related caches on any change
Mitigation:
- ✅ Only affects product caches (isolated Redis database)
- ✅ Cache rebuilds automatically on next request
Impact: Temporary cache miss after product updates
Future Solution:
// Use cache tags (requires Redis 5.0+)
Cache::tags(['products'])->flush();Current State:
- Requires real AWS credentials for S3 upload
- Falls back to local storage if not configured
Mitigation:
- ✅ Automatic fallback to
storage/app/public - ✅ No errors if S3 unavailable
Impact: None for development
Future Solution:
- Use LocalStack for local S3 emulation
- No AWS credentials needed
Current State:
- No authentication/authorization implemented
- API is publicly accessible
Impact: Not production-ready
Future Solution:
// Implement Laravel Sanctum
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('products', ProductController::class);
});Current State:
- No rate limiting on API endpoints
Impact: Vulnerable to abuse/DDoS
Future Solution:
// Add throttle middleware
Route::middleware('throttle:60,1')->group(function () {
Route::apiResource('products', ProductController::class);
});Current State:
- No endpoint to restore soft-deleted products
- Can only restore via database
Impact: Manual intervention required
Future Solution:
// Add restore endpoint
POST /api/products/{id}/restore-
Queue-based ElasticSearch Indexing
- Move ES sync to background jobs
- Improve API response times
- Better error handling and retry logic
-
API Authentication
- Implement Laravel Sanctum
- Add user registration/login
- Protect endpoints with middleware
-
Rate Limiting
- Implement per-user rate limits
- Add throttle middleware
- Return proper 429 responses
-
Cache Tags
- Replace
Cache::flush()with tagged caches - More granular cache invalidation
- Better cache management
- Replace
-
Comprehensive Logging
- Add request/response logging middleware
- Implement log rotation
- Add performance metrics
-
API Versioning
- Implement
/api/v1/prefix - Prepare for future API changes
- Maintain backward compatibility
- Implement
-
Soft Delete Recovery
- Add restore endpoint
- Add "trashed" filter to list endpoint
- Add force delete endpoint (admin only)
-
LocalStack Integration
- S3 emulation for local development
- No AWS credentials needed
- Faster development cycle
-
API Documentation
- Generate OpenAPI/Swagger docs
- Interactive API explorer
- Auto-generated from code
-
Performance Monitoring
- Add Laravel Telescope
- Monitor slow queries
- Track cache hit rates
-
Database Indexing
- Add composite indexes for common queries
- Optimize search performance
- Analyze query patterns
Before deploying to production:
- Enable authentication (Laravel Sanctum)
- Add rate limiting
- Configure CORS properly
- Set up SSL/TLS
- Sanitize error messages (hide stack traces)
- Enable queue-based ES indexing
- Use cache tags
- Configure database indexes
- Set up CDN for static assets
- Configure auto-scaling
- Set up monitoring (New Relic, Datadog, etc.)
- Add error tracking (Sentry, Bugsnag, etc.)
- Configure proper logging
- Add health check endpoint
- Set up uptime monitoring
- Use environment-specific
.envfiles - Set up database backups
- Configure CI/CD pipeline
- Implement graceful shutdown
- Set up staging environment
- API documentation (OpenAPI/Swagger)
- Deployment guide
- Runbook for common issues
- Architecture diagrams
# Start containers
docker-compose up -d
# Stop containers
docker-compose down
# View logs
docker-compose logs -f app
# Access shell
docker-compose exec app bash
# Restart services
docker-compose restart# Run migrations
docker-compose exec app php artisan migrate
# Seed database
docker-compose exec app php artisan db:seed
# Clear cache
docker-compose exec app php artisan cache:clear
# Run tests
docker-compose exec app php artisan test
# Access Tinker
docker-compose exec app php artisan tinkermake install # Complete setup
make up # Start containers
make down # Stop containers
make test # Run tests
make shell # Access app shell
make logs # View logs
make cache-clear # Clear all caches- Laravel Documentation: https://laravel.com/docs/12.x
- ElasticSearch Guide: https://www.elastic.co/guide/
- Redis Documentation: https://redis.io/docs/
- AWS S3 Documentation: https://docs.aws.amazon.com/s3/
This project is open-sourced software licensed under the MIT license.
Your Name
- GitHub: @andersonunsonst
- Email: [email protected]
- Laravel Framework
- ElasticSearch
- Redis
- AWS S3
- Docker
Made with ❤️ using Laravel 12