In Laravel Scout 11, how do the database and collection engines differ from Algolia, Meilisearch and Typesense, and when would you choose each?
answer
- one keeps a separate index, two do not
- collection filters rows in PHP
- database engine: MySQL or PostgreSQL only
- SearchUsingFullText and SearchUsingPrefix
- SCOUT_DRIVER defaults to collection
basics
~20 sThe database and collection engines search your own tables with no separate index; Algolia, Meilisearch and Typesense keep an external index Scout must sync. Collection suits tiny datasets, database suits MySQL or PostgreSQL, external engines add typo tolerance and facets.
solid answer
~40 sIn Scout 11.8 the published `config/scout.php` defaults `SCOUT_DRIVER` to `collection`. The **collection** engine loads the model's rows and filters them in PHP with a case-insensitive substring match over `toSearchableArray()`, so it works on any database, SQLite included, but only for a few hundred records or tests. The **database** engine runs `LIKE` queries, or full-text ones where you mark columns with `#[SearchUsingFullText]`, against your MySQL or PostgreSQL table; it needs no import, and `searchableAs()` and `shouldBeSearchable()` have no effect on it. **Algolia, Meilisearch and Typesense** hold a separate index, which buys relevance ranking, typo tolerance and facets, at the cost of syncing: `scout:import`, a queue, and per-index settings.
code
php · 16 lines<?php
use Laravel\Scout\Attributes\SearchUsingFullText;
use Laravel\Scout\Attributes\SearchUsingPrefix;
// Inside App\Models\Recipe, which uses Laravel\Scout\Searchable
#[SearchUsingPrefix(['title'])]
#[SearchUsingFullText(['ingredients'])]
public function toSearchableArray(): array
{
return [
'title' => $this->title,
'ingredients' => $this->ingredients,
'cuisine' => $this->cuisine,
];
}go deeper
Remember the two families: collection and database search your own tables, while Algolia, Meilisearch and Typesense keep an external index. Know that SCOUT_DRIVER picks the engine.
Explain how the collection engine filters in PHP, how the database engine maps toSearchableArray keys to LIKE or full-text columns, and which model hooks it ignores.
Justify the engine for a real workload: dataset size, typo tolerance and facets, the import and queue burden, and asynchronous indexing on hosted engines.
Weigh adding a search service against staying on the database engine: operating cost, failure modes during outages, and how easily the choice can be reversed later.
## Two families of engine A Scout **engine** (also called a driver) is the class that stores and searches documents. You pick it with `SCOUT_DRIVER`, which feeds the `driver` key in `config/scout.php`. In Scout 11.8 the config comment lists `algolia`, `meilisearch`, `typesense`, `turbopuffer`, `database`, `collection` and `null`, and the default is **`collection`**. The drivers split into two families: - **Engines without an index** (`database`, `collection`) search the model's own table at query time. - **Engines with an index** (Algolia, Meilisearch, Typesense) keep a copy of each document in an external service and search that copy. | Engine | Where it searches | Needs import and sync | Best fit | |---|---|---|---| | `collection` | rows loaded into PHP | no | prototypes, tests, a few hundred rows | | `database` | your MySQL or PostgreSQL table | no | most apps with modest search needs | | Algolia | hosted index | yes | hosted relevance, typo tolerance, analytics | | Meilisearch | self-hosted or hosted index | yes | open-source engine with typo tolerance and facets | | Typesense | self-hosted or hosted index | yes | open-source engine with a typed schema | ## The collection engine The collection engine runs an Eloquent query for the model, applies any Scout `where` constraints as SQL, loads every matching row, and then keeps the models where some value of `toSearchableArray()` contains the search term, compared in lower case. The docs describe it with `Str::is`; the source uses `Str::contains` on lowercased values, so it is a plain substring match. Consequences: 1. it works on every database Laravel supports, including the skeleton's default SQLite; 2. it has no ranking beyond the key order, newest first unless you call `orderBy`; 3. its cost grows with the whole table, so it is unfit for real datasets. ## The database engine The database engine builds a SQL query against the model's table. Each key of `toSearchableArray()` becomes a column to match, so those keys **must be real columns**. By default each column is matched with `LIKE '%term%'`. Two PHP attributes on `toSearchableArray()` change that per column: - `#[SearchUsingPrefix(['title'])]` matches `term%`, which can use an ordinary index; - `#[SearchUsingFullText(['ingredients'])]` uses the database's full-text search, which requires a full-text index you add in a migration. It supports **MySQL and PostgreSQL** only. Because there is no separate index, `searchableAs()`, `getScoutKey()` and `shouldBeSearchable()` have no effect, and a `query()` callback can genuinely filter the SQL. Semantic and hybrid search are available on PostgreSQL with pgvector. ## Engines with an external index Algolia, Meilisearch and Typesense give you relevance tuning, typo tolerance, faceting and speed on large catalogues. The price is operational: - existing rows must be loaded with `scout:import`; - every save makes an HTTP call to the service, so `SCOUT_QUEUE=true` is recommended; - filterable and sortable fields must be declared, for example Meilisearch `filterableAttributes` under `index-settings`, pushed with `scout:sync-index-settings`; - Typesense needs a collection schema, with the key cast to a string; - Algolia and Meilisearch apply writes asynchronously, so a just-saved recipe may not be searchable for a moment. ## Choosing for a recipe catalogue A new app on SQLite with a few dozen recipes can start on `collection`. Once it moves to MySQL or PostgreSQL with thousands of recipes, `database` with a full-text index on `ingredients` is usually enough. Reach for Meilisearch or Typesense when users expect typo-tolerant search such as 'tomatoe' finding tomato dishes, facet counts by cuisine, or relevance ranking that SQL cannot express. A single model can use a different engine from the default by overriding `searchableUsing()` to return `Scout::engine('meilisearch')`. ## Interview traps - **"Scout needs Algolia."** Not in Scout 11.8: the shipped default is `collection`, and the `database` engine covers many real apps without any external service. - **"The database engine is just `whereFullText`."** It builds on the same database features, but it adds Scout's API on top: one `search()` call across several columns, per-column strategies, soft-delete handling and pagination that keeps the search term. - **"Switching engines is free."** Moving from `database` to Meilisearch means running an import, declaring filterable and sortable attributes, adding a queue worker, and accepting that writes appear in search a moment later. - **"`shouldBeSearchable()` hides drafts everywhere."** Not on the database engine, which searches every row; use a `where()` constraint there instead.
- With Laravel Scout's database engine, why can a toSearchableArray() key like 'author_name' break searches?The database engine turns every key of `toSearchableArray()` into a column condition on the model's table. A key that is not a real column, such as a value pulled from a relation, produces SQL against a column that does not exist. With external engines the same key is fine, because it only becomes a field in the stored document.
- What does #[SearchUsingFullText] require in the database before Laravel Scout can use it?A full-text index on that column, created in a migration with the schema builder's full-text index. The attribute only changes the SQL Scout writes; it does not create the index, and without one the full-text query fails or runs as a slow scan, depending on the database.
saying these in an interview costs you the question
- Scout's database engine keeps its own index table that scout:import must fill.
- The collection engine is a production-grade choice for a large catalogue.
- Adding #[SearchUsingFullText] creates the full-text index for you.
- searchableAs() renames the table the database engine searches.
- The database engine runs on SQLite like the rest of the skeleton.