DEV Community

Alex Spinov
Alex Spinov

Posted on

Sonic Has a Free API: Add Lightning-Fast Full-Text Search to Your App Without Running Elasticsearch

Your app needs search. You look at Elasticsearch: 4GB RAM minimum, complex cluster configuration, a JVM that eats memory for breakfast, and a query DSL that requires a PhD to understand. Two weeks later you're still tuning settings. Your 10,000-item dataset doesn't need a distributed cluster — it needs Sonic.

What Sonic Actually Does

Sonic is a lightweight, schema-less search backend written in Rust. It's designed as a lean alternative to Elasticsearch for applications that need fast full-text search without the operational complexity. Where Elasticsearch requires gigabytes of RAM and complex cluster setup, Sonic runs in under 30MB of memory.

Sonic exposes a simple line protocol over TCP — similar to Redis, not HTTP. You push text in, you query text out. No JSON query DSL, no mappings to configure, no index settings to tune. It handles typos with phonetic matching, supports multiple collections and buckets, and is fast enough for real-time search-as-you-type.

The trade-off to understand: Sonic is a search index, not a database. It stores text for search but you retrieve full records from your actual database using the IDs Sonic returns. This separation is the right architecture for most applications.

Quick Start: Run Sonic Locally

docker run -d --name sonic \
  -p 1491:1491 \
  -v sonic_data:/var/lib/sonic/store \
  valeriansaliou/sonic:latest
Enter fullscreen mode Exit fullscreen mode

Install and use the Node.js client:

npm install sonic-channel
Enter fullscreen mode Exit fullscreen mode
const { Ingest, Search } = require("sonic-channel");

const ingestChannel = new Ingest({
  host: "localhost",
  port: 1491,
  auth: "SecretPassword"
});

await ingestChannel.connect();

// Push documents: collection, bucket, object_id, searchable_text
await ingestChannel.push("articles", "default", "article:1",
  "How to build a REST API with Node.js and Express");
await ingestChannel.push("articles", "default", "article:2",
  "Building real-time apps with WebSockets and React");
await ingestChannel.push("articles", "default", "article:3",
  "Rust programming language performance benchmarks");

const searchChannel = new Search({
  host: "localhost",
  port: 1491,
  auth: "SecretPassword"
});

await searchChannel.connect();

const results = await searchChannel.query("articles", "default", "REST API Node");
console.log(results); // ["article:1"]

// Fetch full records using the returned IDs
const articles = await db.articles.findMany({ where: { id: { in: results } } });
Enter fullscreen mode Exit fullscreen mode

3 Practical Use Cases

1. Real-Time Search-as-You-Type

Sonic is fast enough for keypress-level queries — typically under 5ms:

app.get('/api/search', async (req, res) => {
  const { q } = req.query;
  if (!q || q.length < 2) return res.json([]);

  const ids = await searchChannel.query("products", "default", q, { limit: 10 });
  if (ids.length === 0) return res.json([]);

  const products = await db.products.findMany({
    where: { id: { in: ids } },
    select: { id: true, name: true, price: true, image: true }
  });

  res.json(products);
});
Enter fullscreen mode Exit fullscreen mode

10ms total response time for search-as-you-type, even with hundreds of thousands of products indexed.

2. Multi-Collection Search with Buckets

Sonic supports separate collections for different content types:

async function indexContent(item) {
  switch (item.type) {
    case 'article':
      await ingestChannel.push("articles", "en", `article:${item.id}`,
        `${item.title} ${item.body} ${item.tags.join(' ')}`);
      break;
    case 'product':
      // Use buckets to separate by category
      await ingestChannel.push("products", `cat:${item.category}`, `product:${item.id}`,
        `${item.name} ${item.description}`);
      break;
    case 'user':
      await ingestChannel.push("users", "default", `user:${item.id}`,
        `${item.name} ${item.email} ${item.bio || ''}`);
      break;
  }
}

// Search only electronics products
const ids = await searchChannel.query("products", "cat:electronics", searchQuery);
Enter fullscreen mode Exit fullscreen mode

3. Autocomplete with Typo Tolerance

Sonic has built-in suggest/autocomplete and handles common typos automatically:

// User types "prog" — get completions
const suggestions = await searchChannel.suggest("tutorials", "default", "prog");
// ["programming", "program", "progress", "progressive"]

// Typo tolerance: "progrmaming" still finds "programming" results
const results = await searchChannel.query("tutorials", "default", "progrmaming");
// Returns results — phonetic matching handles it automatically
Enter fullscreen mode Exit fullscreen mode

No fuzziness settings to tune, no analyzers to configure. It just works.

Why This Matters

For 90% of applications, Elasticsearch is enormous overkill. Sonic gives you full-text search that actually works — typo tolerance, autocomplete, multi-collection — for about 1% of the operational cost. It runs on a $5/month VPS alongside your main application.

The Rust implementation means it's genuinely fast: benchmarks show Sonic handling 10,000+ queries per second on modest hardware. For applications under 100 million documents, you'll likely never hit its limits.

The real win is operational simplicity. No JVM tuning, no shard configuration, no index lifecycle management. You push text in, you query text out. Sometimes simple really is better — and when it's also fast and memory-efficient, there's no reason to reach for something heavier.


Need custom data extraction or web scraping solutions? I build production-grade scrapers and data pipelines. Check out my Apify actors or email me at spinov001@gmail.com for custom projects.

Follow me for more free API discoveries every week!

Top comments (0)