← Back to articles

How to Add Search to Your App (2026)

Users expect instant, typo-tolerant search. Here's how to add it — from simple PostgreSQL full-text search to dedicated search engines like Meilisearch and Algolia.

The Options

OptionBest ForCostLatency
PostgreSQL full-textSimple search, <100K docsFree (with your DB)10-50ms
MeilisearchSelf-hosted, fast searchFree (self-host)1-20ms
AlgoliaManaged, enterprise$0+ (free tier)1-10ms
TypesenseSelf-hosted alternativeFree (self-host)1-20ms
PagefindStatic sitesFree<5ms

Option 1: PostgreSQL Full-Text Search (Simplest)

Already using Postgres? You already have search.

-- Add a search column
ALTER TABLE articles ADD COLUMN search_vector tsvector;

-- Update it
UPDATE articles SET search_vector = 
  setweight(to_tsvector('english', title), 'A') ||
  setweight(to_tsvector('english', content), 'B');

-- Create an index
CREATE INDEX idx_articles_search ON articles USING gin(search_vector);

-- Search
SELECT title, ts_rank(search_vector, query) as rank
FROM articles, to_tsquery('english', 'typescript & react') as query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 20;

With Drizzle

const results = await db.execute(sql`
  SELECT id, title, ts_rank(search_vector, to_tsquery('english', ${query})) as rank
  FROM articles
  WHERE search_vector @@ to_tsquery('english', ${query})
  ORDER BY rank DESC
  LIMIT 20
`)

Pros: No extra service. Free. Works with your existing database. Cons: No typo tolerance. No instant search. Limited relevance tuning.

Use when: <100K documents and basic search is fine.

Option 2: Meilisearch (Recommended)

Fast, typo-tolerant, easy to set up. Open source.

Setup

# Docker
docker run -p 7700:7700 getmeili/meilisearch:latest

# Or cloud: cloud.meilisearch.com

Index Documents

import { MeiliSearch } from 'meilisearch'

const client = new MeiliSearch({ host: 'http://localhost:7700', apiKey: 'masterKey' })

// Add documents
await client.index('articles').addDocuments([
  { id: 1, title: 'Getting Started with Next.js', content: '...', category: 'tutorial' },
  { id: 2, title: 'React Server Components', content: '...', category: 'guide' },
])

// Configure searchable attributes and ranking
await client.index('articles').updateSettings({
  searchableAttributes: ['title', 'content', 'category'],
  rankingRules: ['words', 'typo', 'proximity', 'attribute', 'sort', 'exactness'],
  filterableAttributes: ['category'],
  sortableAttributes: ['created_at'],
})

Search

const results = await client.index('articles').search('nxtjs tutrial', {
  limit: 20,
  filter: 'category = tutorial',
})

// Returns results even with typos: "nxtjs tutrial" → "Next.js tutorial"

React Component

'use client'
import { useState, useEffect } from 'react'
import { MeiliSearch } from 'meilisearch'

const client = new MeiliSearch({ host: '/api/search', apiKey: 'searchKey' })

function SearchBar() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])

  useEffect(() => {
    if (!query) return setResults([])
    const timer = setTimeout(async () => {
      const { hits } = await client.index('articles').search(query, { limit: 10 })
      setResults(hits)
    }, 200) // Debounce
    return () => clearTimeout(timer)
  }, [query])

  return (
    <div>
      <input value={query} onChange={e => setQuery(e.target.value)} placeholder="Search..." />
      <ul>
        {results.map(hit => (
          <li key={hit.id}>{hit.title}</li>
        ))}
      </ul>
    </div>
  )
}

Pros: Typo-tolerant. Instant (<20ms). Easy setup. Free (self-host). Cons: Extra service to manage. Needs data sync from your DB.

Option 3: Algolia (Managed)

The premium managed option. Fastest search with zero ops.

import algoliasearch from 'algoliasearch'

const client = algoliasearch('APP_ID', 'ADMIN_KEY')
const index = client.initIndex('articles')

// Add documents
await index.saveObjects(articles, { autoGenerateObjectIDIfNotExist: true })

// Search
const { hits } = await index.search('next.js tutorial')

Pros: Fastest. Best relevance. Zero ops. Analytics. A/B testing. Cons: Expensive at scale ($1/1K searches on paid).

Option 4: Pagefind (Static Sites)

For Astro, Hugo, Jekyll, and other static sites:

npx pagefind --site dist

Generates a search index from your static HTML. Client-side search with zero server. ~100KB JavaScript.

<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js"></script>
<div id="search"></div>
<script>
  new PagefindUI({ element: "#search" })
</script>

Keeping Search in Sync

Your database is the source of truth. Search indexes need syncing:

// After creating/updating a document in your DB
await db.insert(articles).values(newArticle)
await meiliClient.index('articles').addDocuments([newArticle])

// Or use webhooks/events
events.on('article.created', async (article) => {
  await meiliClient.index('articles').addDocuments([article])
})

Decision Framework

Static site → Pagefind (free, client-side)
< 100K docs, basic search → PostgreSQL full-text (free)
< 1M docs, good search → Meilisearch (free self-host, $30/mo cloud)
Enterprise, analytics needed → Algolia ($$$)

FAQ

Do I need a separate search engine?

If PostgreSQL full-text search meets your needs (no typo tolerance, <100K docs), no. Add Meilisearch when users complain about search quality.

Meilisearch vs Algolia?

Meilisearch: free to self-host, good enough for most apps. Algolia: managed, faster, better relevance tuning, expensive. Start with Meilisearch, upgrade to Algolia if search is revenue-critical.

How do I handle search on mobile?

Same approach. API calls to your search endpoint. Debounce input to avoid excessive requests.

Bottom Line

Start with PostgreSQL full-text search (free, no extra service). Upgrade to Meilisearch when you need typo tolerance and instant results. Use Algolia only when search is business-critical and budget allows. For static sites, Pagefind is perfect.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.