How to implement typo-tolerant, ultra-fast search in web applications with PostgreSQL, Prisma, and Meilisearch.
Modern web search expects instant feedback. Standard SQL LIKE queries degrade rapidly when filtering thousands of rows or when typos occur. Let's look at how to implement Meilisearch alongside Next.js and Prisma to search listings under 50ms.
Unlike Elasticsearch, which requires massive memory overhead and complex setups, Meilisearch is lightweight, typo-tolerant out of the box, and built for instant search-as-you-type interfaces.
Whenever a property listing is created or updated in PostgreSQL, we must synchronize it to our Meilisearch index. You can do this elegantly using Prisma middleware or DB events:
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
import { MeiliSearch } from 'meilisearch';
const prisma = new PrismaClient();
const searchClient = new MeiliSearch({ host: 'http:class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">//localhost:7700', apiKey: 'masterKey' });
prisma.$use(async (params, next) => {
const result = await next(params);
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Sync to Meilisearch index on listing modifications
if (params.model === 'Listing' && ['create', 'update'].includes(params.action)) {
const searchIndex = searchClient.index('listings');
await searchIndex.addDocuments([{
id: result.id,
title: result.title,
description: result.description,
price: result.price,
city: result.city,
}]);
}
return result;
});Querying the search engine is incredibly straightforward and keeps your API response latency minimal.
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// app/actions/search.ts
'use server';
import { MeiliSearch } from 'meilisearch';
const searchClient = new MeiliSearch({ host: 'http:class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">//localhost:7700', apiKey: 'masterKey' });
export async function searchListings(query: string) {
try {
const index = searchClient.index('listings');
const searchResults = await index.search(query, {
limit: 10,
attributesToHighlight: ['title'],
});
return { success: true, hits: searchResults.hits };
} catch (error) {
console.error('Meilisearch search error:', error);
return { success: false, hits: [] };
}
}By offloading searching search filters to a specialized search-index, your main database operations remain fast, and users get a lightning-fast responsive interface.