Data

/laravel-agent:search:setup

Configure full-text search with Laravel Scout + Meilisearch/Algolia/Typesense

Overview

The /search:setup command configures Laravel Scout with your choice of search engine, making your models searchable. It handles package installation, driver configuration, model setup, and data indexing with support for multiple search engines including Meilisearch, Algolia, Typesense, and the database driver.

Usage

/laravel-agent:search:setup [--driver=<driver>] [--models=<Model1,Model2>]

Examples

# Interactive setup with prompts
/laravel-agent:search:setup

# Setup with Meilisearch
/laravel-agent:search:setup --driver=meilisearch

# Setup Algolia with specific models
/laravel-agent:search:setup --driver=algolia --models=Product,Post

# Setup Typesense
/laravel-agent:search:setup --driver=typesense

# Setup database driver (simple, no dependencies)
/laravel-agent:search:setup --driver=database

Search Engines

Choose the search engine that best fits your needs:

Engine Type Best For Pros
Meilisearch Self-hosted Most projects Free, fast, easy to setup, typo-tolerant
Algolia Hosted (SaaS) High-scale apps Managed, powerful, global CDN
Typesense Self-hosted Open source fans Free, fast, typo-tolerant
Database Built-in Simple search No dependencies, basic LIKE queries

Installation Process

The command handles the complete installation and configuration:

1. Install Scout Package

composer require laravel/scout
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

2. Install Search Driver

# Meilisearch (recommended - free, self-hosted)
composer require meilisearch/meilisearch-php

# Algolia (hosted, pay-per-search)
composer require algolia/algoliasearch-client-php

# Typesense (free, self-hosted)
composer require typesense/typesense-php
composer require typesense/laravel-scout-typesense-driver

3. Configure Environment Variables

4. Add Searchable Trait to Models

5. Import Existing Data

Interactive Prompts

When run without arguments, the command prompts you for configuration:

  1. Search engine?
    • Meilisearch (recommended - free, self-hosted)
    • Algolia (hosted, pay-per-search)
    • Typesense (free, self-hosted)
    • Database (simple, no dependencies)
  2. Which models to make searchable? (multi-select from existing models)
    • [x] Product
    • [x] Post
    • [ ] User
    • [ ] Order
  3. Queue indexing operations?
    • Yes (recommended for production)
    • No (immediate, for development)
  4. Generate Docker Compose? (for Meilisearch/Typesense)
    • Yes
    • No

Configuration Files

config/scout.php

<?php

return [
    'driver' => env('SCOUT_DRIVER', 'meilisearch'),

    'prefix' => env('SCOUT_PREFIX', ''),

    'queue' => env('SCOUT_QUEUE', true),

    'after_commit' => true,

    'chunk' => [
        'searchable' => 500,
        'unsearchable' => 500,
    ],

    'soft_delete' => true,

    'identify' => env('SCOUT_IDENTIFY', false),

    // Meilisearch Configuration
    'meilisearch' => [
        'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
        'key' => env('MEILISEARCH_KEY'),
        'index-settings' => [
            'products' => [
                'filterableAttributes' => ['category_id', 'brand_id', 'status', 'price'],
                'sortableAttributes' => ['price', 'created_at', 'name'],
                'searchableAttributes' => ['name', 'description', 'sku'],
            ],
            'posts' => [
                'filterableAttributes' => ['category_id', 'status', 'author_id'],
                'sortableAttributes' => ['published_at', 'title'],
                'searchableAttributes' => ['title', 'content', 'excerpt'],
            ],
        ],
    ],

    // Algolia Configuration
    'algolia' => [
        'id' => env('ALGOLIA_APP_ID', ''),
        'secret' => env('ALGOLIA_SECRET', ''),
    ],

    // Typesense Configuration
    'typesense' => [
        'api_key' => env('TYPESENSE_API_KEY', ''),
        'nodes' => [
            [
                'host' => env('TYPESENSE_HOST', 'localhost'),
                'port' => env('TYPESENSE_PORT', '8108'),
                'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
            ],
        ],
        'nearest_node' => [
            'host' => env('TYPESENSE_HOST', 'localhost'),
            'port' => env('TYPESENSE_PORT', '8108'),
            'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
        ],
        'connection_timeout_seconds' => 2,
        'healthcheck_interval_seconds' => 30,
        'num_retries' => 3,
        'retry_interval_seconds' => 1,
    ],
];

Environment Variables

# Scout
SCOUT_DRIVER=meilisearch
SCOUT_QUEUE=true
SCOUT_PREFIX=prod_

# Meilisearch
MEILISEARCH_HOST=http://localhost:7700
MEILISEARCH_KEY=your-master-key

# OR Algolia
ALGOLIA_APP_ID=your-app-id
ALGOLIA_SECRET=your-admin-api-key

# OR Typesense
TYPESENSE_API_KEY=your-api-key
TYPESENSE_HOST=localhost
TYPESENSE_PORT=8108

Searchable Model Example

<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

final class Product extends Model
{
    use Searchable;

    /**
     * Get the indexable data array for the model.
     */
    public function toSearchableArray(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'description' => $this->description,
            'sku' => $this->sku,
            'price' => $this->price_cents / 100,
            'category_id' => $this->category_id,
            'brand_id' => $this->brand_id,
            'status' => $this->status,
            'created_at' => $this->created_at->timestamp,
        ];
    }

    /**
     * Get the name of the index.
     */
    public function searchableAs(): string
    {
        return 'products';
    }

    /**
     * Determine if the model should be searchable.
     */
    public function shouldBeSearchable(): bool
    {
        return $this->status === 'active';
    }

    /**
     * Modify the query used for Scout search.
     */
    public function makeSearchableUsing($query)
    {
        return $query->with(['category', 'brand']);
    }
}

Search Controller Example

<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;

final class SearchController extends Controller
{
    public function search(Request $request)
    {
        $request->validate([
            'q' => 'required|string|min:2|max:100',
            'category' => 'nullable|integer',
            'min_price' => 'nullable|numeric|min:0',
            'max_price' => 'nullable|numeric|min:0',
            'sort' => 'nullable|in:relevance,price_asc,price_desc,newest',
        ]);

        $query = Product::search($request->input('q'));

        // Meilisearch filters
        if ($request->filled('category')) {
            $query->where('category_id', $request->integer('category'));
        }

        if ($request->filled('min_price')) {
            $query->where('price', '>=', $request->float('min_price'));
        }

        if ($request->filled('max_price')) {
            $query->where('price', '<=', $request->float('max_price'));
        }

        // Sorting
        match ($request->input('sort')) {
            'price_asc' => $query->orderBy('price', 'asc'),
            'price_desc' => $query->orderBy('price', 'desc'),
            'newest' => $query->orderBy('created_at', 'desc'),
            default => null, // relevance (default)
        };

        return $query->paginate(20);
    }

    /**
     * Instant search for autocomplete.
     */
    public function instant(Request $request)
    {
        $request->validate([
            'q' => 'required|string|min:1|max:50',
        ]);

        $results = Product::search($request->input('q'))
            ->take(5)
            ->get()
            ->map(fn ($product) => [
                'id' => $product->id,
                'name' => $product->name,
                'url' => route('products.show', $product),
                'image' => $product->thumbnail_url,
            ]);

        return response()->json(['results' => $results]);
    }
}

Docker Compose Setup

For self-hosted engines like Meilisearch, the command can generate a Docker Compose file:

# docker-compose.yml
services:
  meilisearch:
    image: getmeili/meilisearch:latest
    ports:
      - "7700:7700"
    volumes:
      - meilisearch-data:/meili_data
    environment:
      - MEILI_MASTER_KEY=your-master-key
      - MEILI_ENV=development

volumes:
  meilisearch-data:

Scout Commands

# Import all records
php artisan scout:import "App\Models\Product"

# Import with fresh index
php artisan scout:flush "App\Models\Product"
php artisan scout:import "App\Models\Product"

# Sync index settings (Meilisearch)
php artisan scout:sync-index-settings

# Queue all models for indexing
php artisan scout:import "App\Models\Product" --chunk=500

Usage Examples

<?php

// Basic search
$products = Product::search('laptop')->get();

// With filters (Meilisearch/Algolia)
$products = Product::search('laptop')
    ->where('category_id', 5)
    ->where('price', '<=', 1000)
    ->get();

// With pagination
$products = Product::search('laptop')->paginate(20);

// Get total count
$count = Product::search('laptop')->count();

// Search specific columns
$products = Product::search('gaming')
    ->within('name')
    ->get();

Command Output

After running the command, you'll receive a comprehensive summary:

## Search Setup Complete

### Packages Installed
- laravel/scout
- meilisearch/meilisearch-php

### Driver: Meilisearch

### Models Configured
| Model | Index | Searchable Fields |
|-------|-------|-------------------|
| Product | products | name, description, sku |
| Post | posts | title, content, excerpt |

### Environment Variables
```env
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://localhost:7700
MEILISEARCH_KEY=
```

### Files Created
- docker-compose.yml (Meilisearch service)
- app/Http/Controllers/SearchController.php

### Commands to Run
```bash
# Start Meilisearch
docker-compose up -d meilisearch

# Import existing data
php artisan scout:import "App\Models\Product"
php artisan scout:import "App\Models\Post"

# Sync index settings
php artisan scout:sync-index-settings
```

### Search Usage
```php
// Basic search
Product::search('laptop')->get();

// With filters
Product::search('laptop')
    ->where('category_id', 5)
    ->where('price', '<=', 1000)
    ->get();
```

### Next Steps
1. Start Meilisearch with `docker-compose up -d`
2. Add MEILISEARCH_KEY to .env
3. Import existing records
4. Test search at /search?q=test

Best Practices

  1. Choose the right driver - Meilisearch for most use cases, Algolia for high-scale
  2. Configure index settings - Define filterable and sortable attributes upfront
  3. Queue indexing - Use SCOUT_QUEUE=true for production to avoid blocking requests
  4. Selective indexing - Use shouldBeSearchable() to only index relevant records
  5. Optimize searchable data - Only include fields needed for search in toSearchableArray()
  6. Monitor search performance - Track search queries and optimize based on usage patterns
  7. Test thoroughly - Verify search results, filters, and sorting work as expected

See Also