The Search Dilemma: When SQL LIKE and Elasticsearch Give You Headaches
When first building search functionality for an e-commerce website or blog, the initial reflex for many developers is to use LIKE '%keyword%' in PostgreSQL or MySQL. This works fine for a few thousand rows. But once the table hits 500,000 records, queries instantly take seconds. Full table scans choke the database connection pool.
The next upgrade path is usually Elasticsearch. While powerful, operating it is notoriously painful. You have to wrestle with JVM tuning, complex mappings, and a cluster that consumes a minimum of 4GB to 8GB of RAM just sitting idle. For small to medium projects, the infrastructure cost and maintenance overhead are simply overkill.
Meilisearch was built to fill exactly that gap. Written in Rust, this search engine consumes only a few hundred megabytes of RAM, delivers sub-50ms query latency, and handles typo-tolerance out of the box without requiring complex analyzer configurations.
Core Concepts in Meilisearch
Before writing code, you only need to grasp these 5 key terms:
- Index: Equivalent to a table in SQL or a collection in MongoDB. This is where documents of the same type are stored.
- Document: A JSON record. Every document must have a unique primary key (defaults to
id). - Typo-tolerance: A built-in mechanism that auto-corrects spelling errors. For example, typing “iphoen” still matches “iPhone”. The engine computes Levenshtein distance on the fly without hurting search performance.
- Searchable Attributes: The list of text fields that the engine scans for keywords.
- Filterable & Sortable Attributes: Fields that enable filtering (such as category, status) or sorting (by price, createdAt).
Hands-on: Integrating Meilisearch into Node.js
Step 1: Launch Meilisearch with Docker
Running Docker is the fastest way to spin up a local environment. Open your terminal and run:
docker run -d --name meilisearch \
-p 7700:7700 \
-e MEILI_MASTER_KEY=my_secure_master_key_123 \
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:v1.7
Once the container starts, verify its status with cURL: curl http://localhost:7700/health. If you get {"status":"available"}, it is ready to go.
Step 2: Install the SDK and Initialize the Connection
Create a project directory and install the official SDK:
mkdir node-meilisearch-demo && cd node-meilisearch-demo
npm init -y
npm install meilisearch dotenv
Create a meiliClient.js file to initialize the connection client:
// meiliClient.js
const { MeiliSearch } = require('meilisearch');
const client = new MeiliSearch({
host: 'http://localhost:7700',
apiKey: 'my_secure_master_key_123',
});
module.exports = client;
Step 3: Create Index and Configure Typo-Tolerance
Next, create a setupIndex.js file to configure the products index:
// setupIndex.js
const client = require('./meiliClient');
async function configureIndex() {
const index = client.index('products');
// Specify searchable fields in order of priority
await index.updateSearchableAttributes([
'name',
'description',
'category'
]);
// Specify filterable and sortable fields
await index.updateFilterableAttributes(['category', 'inStock']);
await index.updateSortableAttributes(['price']);
// Fine-tune typo-tolerance rules
await index.updateTypoTolerance({
enabled: true,
minWordSizeForTypos: {
oneTypo: 4, // Words with 4+ characters allow 1 typo
twoTypos: 8 // Words with 8+ characters allow 2 typos
},
disableOnWords: [],
disableOnAttributes: []
});
console.log('Index configured successfully!');
}
configureIndex();
Run the configuration script: node setupIndex.js.
Step 4: Seed Sample Data (Indexing Documents)
Create a seedData.js file to push product data into the engine:
// seedData.js
const client = require('./meiliClient');
const sampleProducts = [
{
id: 'prod_1',
name: 'Keychron K2 Wireless Mechanical Keyboard',
description: '75% layout mechanical keyboard, Bluetooth or Type-C connectivity, Gateron switches.',
category: 'Computer Accessories',
price: 1850000,
inStock: true
},
{
id: 'prod_2',
name: 'Logitech MX Master 3S Wireless Mouse',
description: 'Premium ergonomic mouse, 8000 DPI sensor, MagSpeed electromagnetic scrolling.',
category: 'Computer Accessories',
price: 2450000,
inStock: true
},
{
id: 'prod_3',
name: 'Dell UltraSharp U2723QE 4K Monitor',
description: '27-inch 4K IPS Black graphic monitor, supports 90W Type-C power delivery.',
category: 'Monitors',
price: 12900000,
inStock: false
}
];
async function seed() {
const index = client.index('products');
const response = await index.addDocuments(sampleProducts);
console.log('Enqueued task UID:', response.taskUid);
}
seed();
Meilisearch processes data indexing asynchronously. The addDocuments call returns a taskUid immediately so your application is never blocked, while background workers handle the indexing under the hood.
Step 5: Write the Search Function with Filtering and Keyword Highlighting
Create a search.js file to test real-world search capabilities:
// search.js
const client = require('./meiliClient');
async function searchProducts(keyword, categoryFilter = null) {
const index = client.index('products');
const searchParams = {
limit: 10,
attributesToHighlight: ['name', 'description'],
highlightPreTag: '<mark>',
highlightPostTag: '</mark>',
};
if (categoryFilter) {
searchParams.filter = `category = "${categoryFilter}"`;
}
const results = await index.search(keyword, searchParams);
console.log(`Found ${results.estimatedTotalHits} results in ${results.processingTimeMs}ms:\n`);
results.hits.forEach((item, idx) => {
console.log(`${idx + 1}. [${item.name}] - Price: ${item.price.toLocaleString('en-US')} VND`);
console.log(` Matching text: ${item._formatted.name}`);
});
}
// Deliberately mistyped: "Logitehc" instead of "Logitech"
searchProducts('Logitehc');
Run the test: node search.js. Meilisearch instantly returns the Logitech MX Master 3S mouse in just 2ms to 4ms, complete with <mark> tags ready for the frontend to render highlighted matches.
In a recent e-commerce project with over 120,000 product SKUs, our team switched from PostgreSQL ILIKE to Meilisearch, slashing search latency from 850ms down to just 12ms. More importantly, it took our team only 2 days to implement the entire search feature instead of spending weeks configuring an Elasticsearch cluster.
Conclusion
Meilisearch bridges the gap between sluggish SQL LIKE queries and the complex management of Elasticsearch. With its clean SDK syntax, responsive typo-tolerance, and minimal memory footprint (just ~150MB for tens of thousands of docs), it is a top-tier choice for Node.js backends. Spin up a container and integrate it into your project today.
