Search Engine
Where this lives / How to run
- Repo: Eliinova/the-one-otc-api
- Scout config:
config/scout.php - Searchable models:
app/Models/Product.php,app/Models/Member.php,app/Models/Carrier.php,app/Models/Manufacturer.php - Local MeiliSearch:
http://localhost:7700 - Re-index command:
php artisan scout:import "App\Models\Product"
| Environment | MeiliSearch host |
|---|---|
| Local | http://localhost:7700 |
| Staging | Managed; see /access for the full URL list and required credentials |
EasyOTC uses MeiliSearch as its primary search engine to provide fast, typo-tolerant search functionality across products, members, carriers.
Overview
MeiliSearch is a powerful, fast, and easy-to-use search engine that provides:
- Typo-tolerant search - Finds results even with spelling mistakes
- Fast search results - Sub-50ms response times
- Faceted search - Filter results by categories
- Real-time indexing - Updates search index immediately
- Simple integration - Easy to set up and maintain
Installation & Setup
1. Install MeiliSearch
# Download and install MeiliSearch binary
curl -L https://install.meilisearch.com | sh
# Start MeiliSearch (run this in a separate terminal)
./meilisearchMeiliSearch will be available at http://localhost:7700
2. Configure Environment Variables
Add these to your .env file:
# MeiliSearch Configuration
MEILISEARCH_HOST=http://localhost:7700
MEILISEARCH_KEY=your-search-key3. Install Laravel Scout
The platform uses Laravel Scout for MeiliSearch integration:
composer require laravel/scoutConfiguration
Scout Configuration
Update config/scout.php:
'default' => env('SCOUT_DRIVER', 'meilisearch'),
'connections' => [
'meilisearch' => [
'driver' => 'meilisearch',
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY'),
],
],Model Configuration
Models that use search have the Searchable trait:
use Laravel\Scout\Searchable;
class Product extends Model
{
use Searchable;
// Define searchable fields
public function toSearchableArray()
{
return $this->toArray();
}
// Define searchable fields for filtering
public function getSearchableFields()
{
return [
'name',
'description',
'sku',
'active_ingredient',
'drug_class',
'strength',
'dosage_form',
];
}
}Searchable Models
Product Search
Searchable Fields:
name- Product namedescription- Product descriptionsku- Stock keeping unitactive_ingredient- Active ingredientdrug_class- Drug classificationstrength- Drug strengthdosage_form- Form of medication
Search Features:
- Full-text search across all fields
- Filter by prescription requirements
- Filter by availability and inventory
- Filter by drug class and active ingredient
Member Search
Searchable Fields:
name- Member full name (from user relationship)email- Member email (from user relationship)phone- Contact phone numberaddress- Street addresscity- Citystate- State/provincezip_code- Postal code
Search Features:
- Search by name or email
- Filter by carrier
- Filter by active status
- Location-based search
Carrier Search
Searchable Fields:
name- Carrier nameslug- URL-friendly identifier
Search Features:
- Search by name
- Filter by active status
Manufacturer Search
Searchable Fields:
name- Manufacturer namedescription- Manufacturer descriptioncountry- Country of origin
Search Features:
- Search by name or description
- Filter by featured status
- Filter by country
Search Implementation
Basic Search
// Search products
$products = Product::search('aspirin')->get();
// Search with filters
$products = Product::search('pain relief')
->where('requires_prescription', false)
->where('is_available', true)
->get();Advanced Search
// Search with multiple filters
$products = Product::search('headache')
->where('drug_class', 'analgesic')
->where('is_available', true)
->where('inventory_count', '>', 0)
->get();
// Search members by location
$members = Member::search('john')
->where('carrier_id', $carrierId)
->where('is_active', true)
->get();Search Results
Search results include:
- Relevance scoring - Results ranked by relevance
- Highlighting - Matched terms highlighted
- Faceted search - Filter options based on results
- Pagination - Large result sets paginated
Indexing
Automatic Indexing
Models are automatically indexed when:
- A new record is created
- An existing record is updated
- A record is deleted (removed from index)
Manual Indexing
# Index all searchable models
php artisan scout:import
# Index specific model
php artisan scout:import "App\Models\Product"
# Flush all indexes
php artisan scout:flushQueue-based Indexing
For better performance, configure queue-based indexing:
// In your model
public function shouldBeSearchable()
{
return $this->is_available;
}Search API Endpoints
Product Search
GET /api/products/search?q=aspirin&prescription=false&available=trueQuery Parameters:
q- Search queryprescription- Filter by prescription requirementavailable- Filter by availabilitydrug_class- Filter by drug classactive_ingredient- Filter by active ingredientpage- Page number for paginationper_page- Results per page
Member Search
GET /api/members/search?q=john&carrier_id=1&active=trueQuery Parameters:
q- Search querycarrier_id- Filter by carrieractive- Filter by active statuspage- Page number for paginationper_page- Results per page
Performance Optimization
Index Optimization
# Optimize MeiliSearch indexes
curl -X POST 'http://localhost:7700/indexes/products/settings/searchable-attributes' \
-H 'Content-Type: application/json' \
-d '["name", "description", "sku", "active_ingredient"]'Caching
Implement caching for frequently searched terms:
// Cache search results
$cacheKey = "search:products:{$query}:{$filters}";
$results = Cache::remember($cacheKey, 300, function () use ($query, $filters) {
return Product::search($query)->where($filters)->get();
});Monitoring & Maintenance
Health Check
# Check MeiliSearch health
curl http://localhost:7700/healthIndex Statistics
# Get index statistics
curl http://localhost:7700/indexes/products/statsBackup & Restore
# Create backup
curl -X POST 'http://localhost:7700/snapshots' \
-H 'Content-Type: application/json' \
-d '{"name": "backup-2024-01-15"}'
# Restore from backup
curl -X POST 'http://localhost:7700/snapshots/backup-2024-01-15/restore'Troubleshooting
Common Issues
Search not working:
- Verify MeiliSearch is running
- Check environment variables
- Ensure models have Searchable trait
- Check index exists and is populated
Slow search performance:
- Optimize searchable attributes
- Implement caching
- Use queue-based indexing
- Monitor MeiliSearch performance
Index out of sync:
- Run manual indexing:
php artisan scout:import - Check for failed jobs in queue
- Verify model events are firing correctly
Debugging
// Enable Scout debugging
SCOUT_DRIVER=meilisearch
SCOUT_QUEUE=false
// Check search queries
Log::info('Search query', [
'query' => $query,
'filters' => $filters,
'results' => $results->count()
]);Best Practices
- Index only necessary fields - Don't index sensitive or large data
- Use appropriate filters - Leverage faceted search for better performance
- Implement caching - Cache frequent search results
- Monitor performance - Track search response times
- Regular maintenance - Backup indexes and monitor disk usage
- Security - Use API keys and restrict access to MeiliSearch admin
This search engine implementation provides fast, relevant search results across all major entities in EasyOTC, enhancing user experience and improving product discovery.