<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Giorgi</title>
    <description>The latest articles on DEV Community by Giorgi (@kencho).</description>
    <link>https://dev.to/kencho</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3322134%2F0f4c15ef-46c9-4cf4-89c3-71421ffe4f29.jpg</url>
      <title>DEV Community: Giorgi</title>
      <link>https://dev.to/kencho</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kencho"/>
    <language>en</language>
    <item>
      <title>The Hidden Costs of Self-Hosting Your Vector Database</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Wed, 22 Apr 2026 14:12:14 +0000</pubDate>
      <link>https://dev.to/kencho/the-hidden-costs-of-self-hosting-your-vector-database-4lo7</link>
      <guid>https://dev.to/kencho/the-hidden-costs-of-self-hosting-your-vector-database-4lo7</guid>
      <description>&lt;p&gt;The pricing page for self-hosted Qdrant says $0. Milvus says $0. pgvector says $0. That's the advertised cost. Then you actually run it in production and the bills start showing up in places you didn't expect.&lt;/p&gt;

&lt;p&gt;This post prices out the real TCO of self-hosting a vector database. Not the compute cost, which everyone already knows. The stuff that shows up on month three when you realize "open source" doesn't mean "free to operate."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Quoted Cost
&lt;/h2&gt;

&lt;p&gt;Every self-hosted vector database pitch starts the same way. "Skip the $500/month Pinecone bill. Self-host for $100 on a single VM." The math looks obvious.&lt;/p&gt;

&lt;p&gt;For a 10M vector workload:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pinecone (managed)&lt;/td&gt;
&lt;td&gt;$500-1,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qdrant self-hosted (1 node)&lt;/td&gt;
&lt;td&gt;$100-300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Milvus self-hosted (1 node)&lt;/td&gt;
&lt;td&gt;$150-400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pgvector on existing Postgres&lt;/td&gt;
&lt;td&gt;$0 (marginal)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those self-hosted numbers are correct. They're also incomplete. What they measure is the VM rental cost. Not the cost of running a vector database on that VM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hidden Cost #1: High Availability
&lt;/h2&gt;

&lt;p&gt;A single-node setup is fine until it's not. When that node reboots, your search is down. When the disk fills up, your search is down. When the Linux kernel pushes a security patch, your search is down.&lt;/p&gt;

&lt;p&gt;The fix is a three-node cluster. Which means the $100 VM becomes three $100 VMs, plus a load balancer, plus whatever you use to coordinate them. Suddenly your $100 pricing-page number is $350-500/month.&lt;/p&gt;

&lt;p&gt;And that's before you've thought about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-AZ deployment&lt;/strong&gt; for cloud-provider availability zone failures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups&lt;/strong&gt; that actually restore (not just snapshot, but tested)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read replicas&lt;/strong&gt; if your query volume saturates a single node&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pinecone does all of this invisibly. You pay $500/month and you get HA. Self-hosting, you pay for the VMs and do the HA engineering yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hidden Cost #2: Engineering Time
&lt;/h2&gt;

&lt;p&gt;This is the big one. And it's the one the pricing comparisons always skip.&lt;/p&gt;

&lt;p&gt;Setting up a production vector database cluster is not a one-weekend job. It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choosing the right VM types, storage tiers, and network config&lt;/li&gt;
&lt;li&gt;Configuring replication, sharding, and consistency settings&lt;/li&gt;
&lt;li&gt;Writing Terraform or Pulumi for reproducible deployments&lt;/li&gt;
&lt;li&gt;Setting up backup/restore (and testing it)&lt;/li&gt;
&lt;li&gt;Monitoring, alerting, and dashboards&lt;/li&gt;
&lt;li&gt;Load testing to find breakpoints before production does&lt;/li&gt;
&lt;li&gt;Documentation so the next engineer can maintain it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Realistic timeline: &lt;strong&gt;3-6 weeks&lt;/strong&gt; for an engineer with prior Kubernetes/infra experience. Longer if they're learning as they go.&lt;/p&gt;

&lt;p&gt;At a $150K/year total comp (a conservative US figure), that's $9,000-$18,000 in upfront engineering cost before a single query runs. Even at EU rates, you're in the $5K-$10K range.&lt;/p&gt;

&lt;p&gt;This isn't the argument to not self-host. It's the argument that "free software" isn't free software plus hardware. It's free software plus hardware plus the labor to operate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hidden Cost #3: The On-Call Tax
&lt;/h2&gt;

&lt;p&gt;Once your vector database is in production, it's part of your oncall rotation. Every alert, every 3am page, every "search is slow" ticket — somebody has to debug it. And that somebody needs to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How HNSW indexing works when it's slow&lt;/li&gt;
&lt;li&gt;Why p99 latency spiked when p50 is fine&lt;/li&gt;
&lt;li&gt;Whether the memory pressure is from the index, the cache, or a leak&lt;/li&gt;
&lt;li&gt;How to safely resize a cluster without downtime&lt;/li&gt;
&lt;li&gt;What to do when a node's disk fills up and the service crashes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is in the Qdrant docs. You learn it in production, usually at the worst time.&lt;/p&gt;

&lt;p&gt;Budget: &lt;strong&gt;0.25 FTE for ongoing operations&lt;/strong&gt; of a small cluster. 0.5-1 FTE for a big one. That's $30K-$150K/year of engineering time, depending on headcount and comp.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hidden Cost #4: Upgrades
&lt;/h2&gt;

&lt;p&gt;Vector databases are young. New versions come out every month. Every version has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New features you want&lt;/li&gt;
&lt;li&gt;Performance improvements you want&lt;/li&gt;
&lt;li&gt;Bug fixes you need&lt;/li&gt;
&lt;li&gt;Breaking changes you don't want&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Upgrading is not &lt;code&gt;apt-get update&lt;/code&gt;. You need to test the new version against your workload, schedule a maintenance window (or do a rolling upgrade, which is more complex), and watch for regressions in the days after.&lt;/p&gt;

&lt;p&gt;If you skip upgrades, you fall behind. Eventually you're 10 versions back and the upgrade path is a migration project. Teams that don't plan for upgrades end up doing a painful rewrite every 18 months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hidden Cost #5: Re-Indexing
&lt;/h2&gt;

&lt;p&gt;At some point you're going to want to change something. A new embedding model. A different vector dimension. A better index type. A new distance metric.&lt;/p&gt;

&lt;p&gt;Any of these means re-embedding and re-indexing your entire dataset. For 10M vectors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compute to re-embed:&lt;/strong&gt; GPU cost to run every document through the new model. See &lt;a href="https://dev.to/blog/what-it-costs-to-search-1m-images"&gt;our 1M image cost breakdown&lt;/a&gt; — roughly $30-100 for 10M items on a spot g6.xlarge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double-capacity window:&lt;/strong&gt; you need the old and new indexes online simultaneously during cutover&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineering time:&lt;/strong&gt; a couple weeks to build the pipeline, test, and migrate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Managed services handle some of this for you. If Pinecone upgrades its underlying index, you don't notice. If you self-host, you're the one scheduling the re-indexing job and watching it run for three days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hidden Cost #6: Observability
&lt;/h2&gt;

&lt;p&gt;A production vector database needs metrics. Not just "is the server up" metrics. The kind of metrics that tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;p50/p95/p99 query latency&lt;/li&gt;
&lt;li&gt;Index memory pressure&lt;/li&gt;
&lt;li&gt;Queue depths for inserts&lt;/li&gt;
&lt;li&gt;Cache hit rates&lt;/li&gt;
&lt;li&gt;Replication lag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The vector database itself exports some of these. Hooking them into Prometheus/Grafana, setting up alerts, and tuning the alert thresholds so you get paged for real problems and not noise — that's a project. A week of engineering time minimum, with ongoing tuning.&lt;/p&gt;

&lt;p&gt;Plus the infra cost. A dedicated monitoring stack runs $50-200/month depending on retention. Datadog or a managed equivalent runs $300-1,000/month for a modest workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hidden Cost #7: Security and Compliance
&lt;/h2&gt;

&lt;p&gt;"We self-host because of compliance" is a common reason. Fair. But self-hosting doesn't automatically make you compliant. You still need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encryption at rest (configured correctly)&lt;/li&gt;
&lt;li&gt;Encryption in transit (with proper cert rotation)&lt;/li&gt;
&lt;li&gt;Network isolation (VPCs, security groups, no public access)&lt;/li&gt;
&lt;li&gt;Access controls (who can query, who can insert, who can drop the collection)&lt;/li&gt;
&lt;li&gt;Audit logs that someone actually reviews&lt;/li&gt;
&lt;li&gt;Regular vulnerability scans and patching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most managed vector databases are SOC 2 certified, so your compliance story piggybacks on theirs. Self-hosted, you own the entire compliance perimeter. That's weeks of work for initial compliance and ongoing effort to keep it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real TCO
&lt;/h2&gt;

&lt;p&gt;Let's add it up for a 10M vector workload running on self-hosted Qdrant with HA:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cost Category&lt;/th&gt;
&lt;th&gt;Monthly&lt;/th&gt;
&lt;th&gt;Annual&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3-node cluster VMs&lt;/td&gt;
&lt;td&gt;$350&lt;/td&gt;
&lt;td&gt;$4,200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load balancer + networking&lt;/td&gt;
&lt;td&gt;$30&lt;/td&gt;
&lt;td&gt;$360&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring stack&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;td&gt;$1,200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backups + storage&lt;/td&gt;
&lt;td&gt;$50&lt;/td&gt;
&lt;td&gt;$600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infrastructure subtotal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$530&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$6,360&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Initial build (amortized over 2 yrs)&lt;/td&gt;
&lt;td&gt;$500&lt;/td&gt;
&lt;td&gt;$6,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ongoing ops (0.25 FTE)&lt;/td&gt;
&lt;td&gt;$2,500&lt;/td&gt;
&lt;td&gt;$30,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;True TCO&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$3,530&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$42,360&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The infrastructure is $530/month. The real cost is $3,530/month. That's what the pricing comparisons miss.&lt;/p&gt;

&lt;p&gt;Compare to Pinecone at $500-1,500/month for the same workload. It's 2-7x cheaper when you include engineering time.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Self-Hosting Actually Wins
&lt;/h2&gt;

&lt;p&gt;Self-hosting isn't always a bad choice. It wins in specific scenarios:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Very large workloads.&lt;/strong&gt; Past 100M vectors, the managed pricing gets steep. Self-hosted Qdrant or Milvus at that scale can be 3-5x cheaper, and the engineering overhead amortizes over a bigger bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Existing platform team.&lt;/strong&gt; If you already have a platform team running Kubernetes and databases, adding a vector database is marginal. The engineering tax is already paid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hard compliance or data residency.&lt;/strong&gt; Some regulated environments require data to never leave your VPC. Self-hosting might be the only option, and the engineering cost is just the cost of doing business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Research or experimentation.&lt;/strong&gt; For non-production workloads where uptime doesn't matter, self-hosting is fine. Spin up a single Qdrant node, don't worry about HA, move on.&lt;/p&gt;

&lt;p&gt;For everyone else — small teams, startups, internal tools, prototypes — managed wins. The math on engineering time is brutal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Alternative
&lt;/h2&gt;

&lt;p&gt;There's a third option that doesn't show up in "self-hosted vs managed" comparisons: not running a vector database at all.&lt;/p&gt;

&lt;p&gt;For a lot of use cases, you don't need to manage a vector database. You need search that works. &lt;a href="https://vecstore.app" rel="noopener noreferrer"&gt;Vecstore&lt;/a&gt; is one option in this category — a search API that handles embedding, vector storage, and query serving in one thing. No cluster to run, no index to tune, no engineering team to maintain.&lt;/p&gt;

&lt;p&gt;This is the real question to ask before choosing a vector database: do I need a vector database, or do I need search? If the answer is search, the vector database is just infrastructure overhead.&lt;/p&gt;

&lt;p&gt;We covered this in more detail in &lt;a href="https://dev.to/blog/you-dont-need-a-vector-database"&gt;You Don't Need a Vector Database&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The quoted cost of self-hosting a vector database is real. It's also the smallest line item on the actual bill. Engineering time, on-call burden, upgrades, and operations dwarf the compute cost in almost every scenario.&lt;/p&gt;

&lt;p&gt;Before picking self-hosted based on the pricing page, run the real TCO numbers. Include engineering time at your team's actual rate. Include the on-call tax. Include the re-indexing you'll do in 12 months when you switch embedding models.&lt;/p&gt;

&lt;p&gt;If the math still works, self-host. If not, the managed option was probably cheaper all along.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vecstore.app" rel="noopener noreferrer"&gt;See how Vecstore compares&lt;/a&gt; or &lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;sign up for the free tier&lt;/a&gt; to skip the infra conversation entirely.&lt;/p&gt;

</description>
      <category>semanticsearch</category>
      <category>comparisons</category>
    </item>
    <item>
      <title>How to Build Pinterest-Style Visual Discovery</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Wed, 22 Apr 2026 14:12:04 +0000</pubDate>
      <link>https://dev.to/kencho/how-to-build-pinterest-style-visual-discovery-2hh5</link>
      <guid>https://dev.to/kencho/how-to-build-pinterest-style-visual-discovery-2hh5</guid>
      <description>&lt;p&gt;Most image search tutorials build a search bar. Type "red sneaker", get red sneakers. That's search.&lt;/p&gt;

&lt;p&gt;Pinterest isn't search. Pinterest is &lt;em&gt;discovery&lt;/em&gt;. You open the app, scroll a masonry grid of images, tap one that catches your eye, and suddenly your whole feed shifts toward that taste. No keywords typed. No search button pressed. The system figures out what you like from what you click and serves more of it.&lt;/p&gt;

&lt;p&gt;This tutorial builds that. A Pinterest-style discovery feed where the grid reacts in real-time to what the user engages with, using visual similarity under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A masonry grid feed with infinite scroll&lt;/li&gt;
&lt;li&gt;A React component that loads images from your backend&lt;/li&gt;
&lt;li&gt;A backend that tracks clicks and biases future results toward what the user engaged with&lt;/li&gt;
&lt;li&gt;Visual similarity search powered by Vecstore&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key difference from a normal search: the feed has no query box. It just learns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 18+&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;Vecstore account&lt;/a&gt; (free tier works)&lt;/li&gt;
&lt;li&gt;An image database seeded with a few hundred images&lt;/li&gt;
&lt;li&gt;Basic React knowledge&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the Discovery Loop Works
&lt;/h2&gt;

&lt;p&gt;Before the code, the mental model. Pinterest's "related pins" isn't magic. It's three things stacked together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Visual embeddings&lt;/strong&gt; — every image is a vector in a shared space&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User signal&lt;/strong&gt; — when a user taps an image, that's a signal they like that style&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feed bias&lt;/strong&gt; — the next batch of images is weighted toward things similar to what they tapped&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the loop is: show images → track which ones get tapped → use tapped images as "query" for the next batch → repeat.&lt;/p&gt;

&lt;p&gt;The more the user interacts, the more the feed converges on their taste. No explicit search, no categories, no tags.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Build the Backend
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;server.js&lt;/code&gt;. This handles initial feed, related images, and tap tracking.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-blue"&gt;import&lt;/span&gt; express &lt;span class="hl-blue"&gt;from&lt;/span&gt; &lt;span class="hl-green"&gt;'express'&lt;/span&gt;;
&lt;span class="hl-blue"&gt;import&lt;/span&gt; cors &lt;span class="hl-blue"&gt;from&lt;/span&gt; &lt;span class="hl-green"&gt;'cors'&lt;/span&gt;;

&lt;span class="hl-blue"&gt;const&lt;/span&gt; app = express();
app.use(cors());
app.use(express.json());

&lt;span class="hl-blue"&gt;const&lt;/span&gt; API_KEY = process.env.VECSTORE_API_KEY;
&lt;span class="hl-blue"&gt;const&lt;/span&gt; DB_ID = process.env.VECSTORE_DB_ID;
&lt;span class="hl-blue"&gt;const&lt;/span&gt; BASE = &lt;span class="hl-green"&gt;'https://api.vecstore.app/api'&lt;/span&gt;;
&lt;span class="hl-blue"&gt;const&lt;/span&gt; HEADERS = {
  &lt;span class="hl-green"&gt;'X-API-Key'&lt;/span&gt;: API_KEY,
  &lt;span class="hl-green"&gt;'Content-Type'&lt;/span&gt;: &lt;span class="hl-green"&gt;'application/json'&lt;/span&gt;,
};

&lt;span class="hl-gray"&gt;// naive in-memory session store. use Redis in production.&lt;/span&gt;
&lt;span class="hl-blue"&gt;const&lt;/span&gt; sessions = &lt;span class="hl-blue"&gt;new&lt;/span&gt; Map();

&lt;span class="hl-gray"&gt;// GET /feed?session=xxx&amp;amp;cursor=0 - return next batch&lt;/span&gt;
app.get(&lt;span class="hl-green"&gt;'/feed'&lt;/span&gt;, &lt;span class="hl-blue"&gt;async&lt;/span&gt; (req, res) =&amp;gt; {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; { session, cursor = 0 } = req.query;
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; state = sessions.get(session) || { tapped: [], seen: &lt;span class="hl-blue"&gt;new&lt;/span&gt; Set() };

  &lt;span class="hl-blue"&gt;let&lt;/span&gt; results = [];

  &lt;span class="hl-blue"&gt;if&lt;/span&gt; (state.tapped.length === 0) {
    &lt;span class="hl-gray"&gt;// cold start - random popular items&lt;/span&gt;
    results = &lt;span class="hl-blue"&gt;await&lt;/span&gt; getColdStart(parseInt(cursor));
  } &lt;span class="hl-blue"&gt;else&lt;/span&gt; {
    &lt;span class="hl-gray"&gt;// bias toward recently tapped images&lt;/span&gt;
    results = &lt;span class="hl-blue"&gt;await&lt;/span&gt; getBiasedFeed(state.tapped, state.seen);
  }

  &lt;span class="hl-gray"&gt;// mark as seen so we don't show again&lt;/span&gt;
  results.forEach(r =&amp;gt; state.seen.add(r.vector_id));
  sessions.set(session, state);

  res.json({
    results,
    nextCursor: parseInt(cursor) + results.length,
  });
});

&lt;span class="hl-gray"&gt;// POST /tap - user interacted with an image&lt;/span&gt;
app.post(&lt;span class="hl-green"&gt;'/tap'&lt;/span&gt;, (req, res) =&amp;gt; {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; { session, vector_id, image_url } = req.body;
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; state = sessions.get(session) || { tapped: [], seen: &lt;span class="hl-blue"&gt;new&lt;/span&gt; Set() };

  &lt;span class="hl-gray"&gt;// keep last 5 taps as query signal&lt;/span&gt;
  state.tapped = [{ vector_id, image_url }, ...state.tapped].slice(0, 5);
  sessions.set(session, state);

  res.json({ ok: &lt;span class="hl-blue"&gt;true&lt;/span&gt; });
});

&lt;span class="hl-gray"&gt;// GET /similar/:id - full-page "related" view&lt;/span&gt;
app.get(&lt;span class="hl-green"&gt;'/similar/:id'&lt;/span&gt;, &lt;span class="hl-blue"&gt;async&lt;/span&gt; (req, res) =&amp;gt; {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; result = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(`${BASE}/databases/${DB_ID}/search`, {
    method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
    headers: HEADERS,
    body: JSON.stringify({ vector_id: req.params.id, top_k: 30 }),
  });
  res.json(&lt;span class="hl-blue"&gt;await&lt;/span&gt; result.json());
});

app.listen(3001, () =&amp;gt; console.log(&lt;span class="hl-green"&gt;'Running on 3001'&lt;/span&gt;));
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;
  
  
  Step 2: Cold Start and Biased Feed Logic
&lt;/h2&gt;

&lt;p&gt;Two helper functions do the real work.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-gray"&gt;// Cold start - no user signal yet. Use random seed queries
// to surface variety. In production, replace with your
// trending/popular items.&lt;/span&gt;
&lt;span class="hl-blue"&gt;const&lt;/span&gt; COLD_START_QUERIES = [
  &lt;span class="hl-green"&gt;'minimalist home decor'&lt;/span&gt;,
  &lt;span class="hl-green"&gt;'vintage fashion'&lt;/span&gt;,
  &lt;span class="hl-green"&gt;'modern architecture'&lt;/span&gt;,
  &lt;span class="hl-green"&gt;'nature photography'&lt;/span&gt;,
  &lt;span class="hl-green"&gt;'food styling'&lt;/span&gt;,
  &lt;span class="hl-green"&gt;'street art'&lt;/span&gt;,
];

&lt;span class="hl-blue"&gt;async function&lt;/span&gt; getColdStart(cursor) {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; query = COLD_START_QUERIES[
    Math.floor(Math.random() * COLD_START_QUERIES.length)
  ];

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; result = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(`${BASE}/databases/${DB_ID}/search`, {
    method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
    headers: HEADERS,
    body: JSON.stringify({ query, top_k: 20 }),
  });

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; data = &lt;span class="hl-blue"&gt;await&lt;/span&gt; result.json();
  &lt;span class="hl-blue"&gt;return&lt;/span&gt; data.results || [];
}

&lt;span class="hl-gray"&gt;// Biased feed - take last few tapped images and search
// for similar ones. Mix results so feed doesn't converge too fast.&lt;/span&gt;
&lt;span class="hl-blue"&gt;async function&lt;/span&gt; getBiasedFeed(tapped, seen) {
  &lt;span class="hl-gray"&gt;// pick one tapped image at random, weighted toward recent&lt;/span&gt;
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; weighted = tapped.flatMap((t, i) =&amp;gt;
    Array(tapped.length - i).fill(t)
  );
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; pick = weighted[Math.floor(Math.random() * weighted.length)];

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; result = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(`${BASE}/databases/${DB_ID}/search`, {
    method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
    headers: HEADERS,
    body: JSON.stringify({
      image_url: pick.image_url,
      top_k: 30,
    }),
  });

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; data = &lt;span class="hl-blue"&gt;await&lt;/span&gt; result.json();

  &lt;span class="hl-gray"&gt;// filter out already-seen items and cap to 20&lt;/span&gt;
  &lt;span class="hl-blue"&gt;return&lt;/span&gt; (data.results || [])
    .filter(r =&amp;gt; !seen.has(r.vector_id))
    .slice(0, 20);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Two things worth noting:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weighted recency.&lt;/strong&gt; Recent taps matter more than old ones. The &lt;code&gt;weighted&lt;/code&gt; array duplicates newer items so random picks lean toward recent interactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seen filtering.&lt;/strong&gt; Without this, the feed loops. User taps a blue chair, sees similar chairs, taps one, sees the same chairs again. Tracking seen IDs per session keeps the feed fresh.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Build the Masonry Grid
&lt;/h2&gt;

&lt;p&gt;Install the frontend dependencies:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-blue"&gt;npm install&lt;/span&gt; masonic&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;masonic&lt;/code&gt; handles the tricky part — virtualized masonry layout with variable heights. It renders tens of thousands of cells without lag.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;DiscoveryFeed.jsx&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-blue"&gt;import&lt;/span&gt; { useState, useEffect, useCallback } &lt;span class="hl-blue"&gt;from&lt;/span&gt; &lt;span class="hl-green"&gt;'react'&lt;/span&gt;;
&lt;span class="hl-blue"&gt;import&lt;/span&gt; { Masonry } &lt;span class="hl-blue"&gt;from&lt;/span&gt; &lt;span class="hl-green"&gt;'masonic'&lt;/span&gt;;

&lt;span class="hl-blue"&gt;const&lt;/span&gt; API = &lt;span class="hl-green"&gt;'http://localhost:3001'&lt;/span&gt;;

&lt;span class="hl-gray"&gt;// persistent session id - simple random string in localStorage&lt;/span&gt;
&lt;span class="hl-blue"&gt;function&lt;/span&gt; getSessionId() {
  &lt;span class="hl-blue"&gt;let&lt;/span&gt; id = localStorage.getItem(&lt;span class="hl-green"&gt;'vs-session'&lt;/span&gt;);
  &lt;span class="hl-blue"&gt;if&lt;/span&gt; (!id) {
    id = Math.random().toString(36).slice(2);
    localStorage.setItem(&lt;span class="hl-green"&gt;'vs-session'&lt;/span&gt;, id);
  }
  &lt;span class="hl-blue"&gt;return&lt;/span&gt; id;
}

&lt;span class="hl-blue"&gt;export default function&lt;/span&gt; DiscoveryFeed() {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; [items, setItems] = useState([]);
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; [cursor, setCursor] = useState(0);
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; [loading, setLoading] = useState(&lt;span class="hl-blue"&gt;false&lt;/span&gt;);
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; session = getSessionId();

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; loadMore = useCallback(&lt;span class="hl-blue"&gt;async&lt;/span&gt; () =&amp;gt; {
    &lt;span class="hl-blue"&gt;if&lt;/span&gt; (loading) &lt;span class="hl-blue"&gt;return&lt;/span&gt;;
    setLoading(&lt;span class="hl-blue"&gt;true&lt;/span&gt;);

    &lt;span class="hl-blue"&gt;const&lt;/span&gt; res = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(
      `${API}/feed?session=${session}&amp;amp;cursor=${cursor}`
    );
    &lt;span class="hl-blue"&gt;const&lt;/span&gt; data = &lt;span class="hl-blue"&gt;await&lt;/span&gt; res.json();

    setItems(prev =&amp;gt; [...prev, ...data.results]);
    setCursor(data.nextCursor);
    setLoading(&lt;span class="hl-blue"&gt;false&lt;/span&gt;);
  }, [cursor, loading, session]);

  useEffect(() =&amp;gt; {
    loadMore();
  }, []);

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; handleTap = &lt;span class="hl-blue"&gt;async&lt;/span&gt; (item) =&amp;gt; {
    &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(`${API}/tap`, {
      method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
      headers: { &lt;span class="hl-green"&gt;'Content-Type'&lt;/span&gt;: &lt;span class="hl-green"&gt;'application/json'&lt;/span&gt; },
      body: JSON.stringify({
        session,
        vector_id: item.vector_id,
        image_url: item.metadata.image_url,
      }),
    });

    &lt;span class="hl-gray"&gt;// after tap, prepend fresh batch to feed&lt;/span&gt;
    &lt;span class="hl-blue"&gt;const&lt;/span&gt; res = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(
      `${API}/feed?session=${session}&amp;amp;cursor=${cursor}`
    );
    &lt;span class="hl-blue"&gt;const&lt;/span&gt; data = &lt;span class="hl-blue"&gt;await&lt;/span&gt; res.json();
    setItems(prev =&amp;gt; [...data.results, ...prev]);
    setCursor(data.nextCursor);
  };

  &lt;span class="hl-blue"&gt;return&lt;/span&gt; (
    &amp;lt;Masonry
      items={items}
      columnGutter={12}
      columnWidth={240}
      overscanBy={5}
      onRender={(startIdx, stopIdx, items) =&amp;gt; {
        &lt;span class="hl-gray"&gt;// trigger load when near bottom&lt;/span&gt;
        &lt;span class="hl-blue"&gt;if&lt;/span&gt; (stopIdx &amp;gt;= items.length - 10) loadMore();
      }}
      render={({ data }) =&amp;gt; (
        &amp;lt;div onClick={() =&amp;gt; handleTap(data)}
             style={{ cursor: &lt;span class="hl-green"&gt;'pointer'&lt;/span&gt; }}&amp;gt;
          &amp;lt;img
            src={data.metadata.image_url}
            alt=""
            style={{
              width: &lt;span class="hl-green"&gt;'100%'&lt;/span&gt;,
              display: &lt;span class="hl-green"&gt;'block'&lt;/span&gt;,
              borderRadius: 8,
            }}
            loading="lazy"
          /&amp;gt;
        &amp;lt;/div&amp;gt;
      )}
    /&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Three things happening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Masonry&lt;/code&gt;&lt;/strong&gt; handles layout and virtualization. Columns auto-size to &lt;code&gt;columnWidth={240}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;onRender&lt;/code&gt;&lt;/strong&gt; fires as rows mount. When user is within 10 items of the bottom, load the next batch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;handleTap&lt;/code&gt;&lt;/strong&gt; sends the tap to the backend, then refetches so the feed reacts immediately.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last part is the magic. The user taps an image → backend records it → next fetch biases toward similar images → feed shifts in real-time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Add the Related Modal
&lt;/h2&gt;

&lt;p&gt;Pinterest also has a "related" view when you click into a pin. Full-page view of the pin with similar images below. Here's a minimal version.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-blue"&gt;import&lt;/span&gt; { useState } &lt;span class="hl-blue"&gt;from&lt;/span&gt; &lt;span class="hl-green"&gt;'react'&lt;/span&gt;;

&lt;span class="hl-blue"&gt;export function&lt;/span&gt; PinModal({ item, onClose }) {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; [related, setRelated] = useState([]);

  useEffect(() =&amp;gt; {
    &lt;span class="hl-blue"&gt;if&lt;/span&gt; (!item) &lt;span class="hl-blue"&gt;return&lt;/span&gt;;
    fetch(`${API}/similar/${item.vector_id}`)
      .then(r =&amp;gt; r.json())
      .then(data =&amp;gt; setRelated(data.results || []));
  }, [item]);

  &lt;span class="hl-blue"&gt;if&lt;/span&gt; (!item) &lt;span class="hl-blue"&gt;return null&lt;/span&gt;;

  &lt;span class="hl-blue"&gt;return&lt;/span&gt; (
    &amp;lt;div onClick={onClose}
         style={{
           position: &lt;span class="hl-green"&gt;'fixed'&lt;/span&gt;, inset: 0,
           background: &lt;span class="hl-green"&gt;'rgba(0,0,0,0.85)'&lt;/span&gt;,
           overflow: &lt;span class="hl-green"&gt;'auto'&lt;/span&gt;, padding: 24, zIndex: 1000,
         }}&amp;gt;
      &amp;lt;div onClick={e =&amp;gt; e.stopPropagation()}
           style={{ maxWidth: 900, margin: &lt;span class="hl-green"&gt;'0 auto'&lt;/span&gt; }}&amp;gt;
        &amp;lt;img src={item.metadata.image_url}
          style={{ width: &lt;span class="hl-green"&gt;'100%'&lt;/span&gt;, borderRadius: 12 }} /&amp;gt;
        &amp;lt;h3 style={{ color: &lt;span class="hl-green"&gt;'white'&lt;/span&gt;, marginTop: 24 }}&amp;gt;More like this&amp;lt;/h3&amp;gt;
        &amp;lt;div style={{
          display: &lt;span class="hl-green"&gt;'grid'&lt;/span&gt;,
          gridTemplateColumns: &lt;span class="hl-green"&gt;'repeat(auto-fill,minmax(180px,1fr))'&lt;/span&gt;,
          gap: 12,
        }}&amp;gt;
          {related.map(r =&amp;gt; (
            &amp;lt;img key={r.vector_id}
              src={r.metadata.image_url}
              style={{ width: &lt;span class="hl-green"&gt;'100%'&lt;/span&gt;,
                       aspectRatio: 1,
                       objectFit: &lt;span class="hl-green"&gt;'cover'&lt;/span&gt;,
                       borderRadius: 8 }} /&amp;gt;
          ))}
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Wire it into &lt;code&gt;DiscoveryFeed&lt;/code&gt; by setting a &lt;code&gt;selectedItem&lt;/code&gt; state on tap and rendering &lt;code&gt;&amp;lt;PinModal item={selectedItem} onClose={...} /&amp;gt;&lt;/code&gt; at the bottom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tuning the Discovery Loop
&lt;/h2&gt;

&lt;p&gt;The basic version works. But a few knobs matter for the feel of the feed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tap window.&lt;/strong&gt; Right now the backend keeps the last 5 taps as signal. Too few and the feed over-reacts to any single tap. Too many and it never adapts. 5-10 is a good range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recency weight.&lt;/strong&gt; The weighted picker biases toward recent taps. You can strengthen this by using exponential weights (&lt;code&gt;Math.pow(2, tapped.length - i - 1)&lt;/code&gt;) instead of linear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Variety injection.&lt;/strong&gt; Pure similarity makes the feed claustrophobic. Every 5th or 6th item, inject something random from cold-start queries. Breaks the filter bubble and surfaces new things the user might like.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-blue"&gt;async function&lt;/span&gt; getBiasedFeed(tapped, seen) {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; similar = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetchSimilar(tapped, seen, 16);
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; variety = &lt;span class="hl-blue"&gt;await&lt;/span&gt; getColdStart(0);

  &lt;span class="hl-gray"&gt;// interleave - similar, similar, similar, variety, repeat&lt;/span&gt;
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; mixed = [];
  &lt;span class="hl-blue"&gt;let&lt;/span&gt; vi = 0;
  &lt;span class="hl-blue"&gt;for&lt;/span&gt; (&lt;span class="hl-blue"&gt;let&lt;/span&gt; i = 0; i &amp;lt; similar.length; i++) {
    mixed.push(similar[i]);
    &lt;span class="hl-blue"&gt;if&lt;/span&gt; ((i + 1) % 4 === 0 &amp;amp;&amp;amp; variety[vi]) {
      mixed.push(variety[vi++]);
    }
  }
  &lt;span class="hl-blue"&gt;return&lt;/span&gt; mixed;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Negative signal.&lt;/strong&gt; Pinterest uses "not interested" buttons. You can track skipped items (scrolled past without tapping) and down-weight similar images. This requires more instrumentation but drastically improves feed quality over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to Keep in Mind
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Session state in production.&lt;/strong&gt; The in-memory &lt;code&gt;Map&lt;/code&gt; works for a demo. For real users, use Redis or a persistent store keyed to user ID. Taps should survive across devices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image loading performance.&lt;/strong&gt; Masonry grids are image-heavy. Use &lt;code&gt;loading="lazy"&lt;/code&gt; on every image. Serve multiple sizes and let the browser pick with &lt;code&gt;srcSet&lt;/code&gt;. For production, put images on a CDN with automatic format conversion (WebP, AVIF).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vecstore &lt;code&gt;vector_id&lt;/code&gt; search.&lt;/strong&gt; The &lt;code&gt;/similar/:id&lt;/code&gt; endpoint uses &lt;code&gt;vector_id&lt;/code&gt; instead of &lt;code&gt;image_url&lt;/code&gt;. This is faster because Vecstore doesn't need to re-embed — it already has the vector. Use this whenever you have the ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost management.&lt;/strong&gt; Every scroll triggers API calls. For a popular feed, that's a lot of queries. Cache the first few cold-start batches (they're the same for new users). Cache related results per vector ID. The traffic goes from "one API call per scroll" to "one API call per tap" fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold start is a design problem.&lt;/strong&gt; The six cold-start queries above are placeholders. For a real product, replace them with trending items, editorial picks, or a curated onboarding set. The first 30 seconds of a new user's session determine whether they stick.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Else You Can Do
&lt;/h2&gt;

&lt;p&gt;The same discovery pattern works for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product discovery&lt;/strong&gt; on an e-commerce store (what we covered in &lt;a href="https://dev.to/blog/how-to-add-find-similar-products"&gt;find similar products&lt;/a&gt; but as a whole feed instead of a sidebar)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dating apps&lt;/strong&gt; where "taps" are swipe rights and the feed learns your type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real estate browsing&lt;/strong&gt; where clicking a property biases toward similar listings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recipe discovery&lt;/strong&gt; where engagement shapes the next batch toward your taste&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's the same loop. Images in, visual embeddings out, engagement signal drives the next batch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Full setup: an Express backend with three routes, a React component with a masonry grid, and session-based tap tracking. The discovery loop is fewer than 200 lines of code.&lt;/p&gt;

&lt;p&gt;The thing that makes this feel like Pinterest isn't the layout. It's the feedback loop. Every tap reshapes the next batch. Users don't realize they're training the feed — they just feel like the app "gets them." That's what keeps them scrolling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;Get started with Vecstore&lt;/a&gt; - free tier includes enough credits to build and test a discovery feed.&lt;/p&gt;

</description>
      <category>imagesearch</category>
      <category>tutorials</category>
    </item>
    <item>
      <title>How to Add Image Search to a Shopify Store</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Thu, 16 Apr 2026 06:04:54 +0000</pubDate>
      <link>https://dev.to/kencho/how-to-add-image-search-to-a-shopify-store-3kph</link>
      <guid>https://dev.to/kencho/how-to-add-image-search-to-a-shopify-store-3kph</guid>
      <description>&lt;p&gt;Most Shopify search bars only understand keywords. A customer looking for "that kind of minimalist wooden shelf" gets a page full of irrelevant results or nothing at all. They had a clear picture in their head, but the search bar couldn't understand it.&lt;/p&gt;

&lt;p&gt;Image search fixes this. A customer uploads a photo or screenshot and your store finds visually similar products from your catalog. No tags, no keywords, no hoping the customer describes the product the same way you did.&lt;/p&gt;

&lt;p&gt;This tutorial walks through the full setup: syncing your Shopify product catalog into a searchable image database, building the search backend, and adding it to your storefront.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A script that pulls all products from your Shopify store and indexes their images&lt;/li&gt;
&lt;li&gt;A backend that handles text-to-image and image-to-image search&lt;/li&gt;
&lt;li&gt;A search widget for your Shopify storefront&lt;/li&gt;
&lt;li&gt;A "similar products" section on product pages&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Shopify store with products&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/generate-app-access-tokens-admin" rel="noopener noreferrer"&gt;Shopify custom app&lt;/a&gt; with &lt;code&gt;read_products&lt;/code&gt; scope&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;Vecstore account&lt;/a&gt; (free tier works)&lt;/li&gt;
&lt;li&gt;An image database created in the Vecstore dashboard&lt;/li&gt;
&lt;li&gt;Node.js 18+&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Sync Your Shopify Catalog
&lt;/h2&gt;

&lt;p&gt;First, pull your products from Shopify and insert their images into Vecstore. Create &lt;code&gt;sync-catalog.js&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-blue"&gt;const&lt;/span&gt; SHOPIFY_STORE = &lt;span class="hl-green"&gt;'your-store.myshopify.com'&lt;/span&gt;;
&lt;span class="hl-blue"&gt;const&lt;/span&gt; SHOPIFY_TOKEN = process.env.SHOPIFY_ACCESS_TOKEN;
&lt;span class="hl-blue"&gt;const&lt;/span&gt; VECSTORE_KEY = process.env.VECSTORE_API_KEY;
&lt;span class="hl-blue"&gt;const&lt;/span&gt; VECSTORE_DB = process.env.VECSTORE_DB_ID;

&lt;span class="hl-blue"&gt;async function&lt;/span&gt; fetchProducts(cursor = &lt;span class="hl-blue"&gt;null&lt;/span&gt;) {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; query = `{
    products(first: 50${cursor ? `, after: "${cursor}"` : ''}) {
      edges {
        cursor
        node {
          id
          title
          handle
          priceRangeV2 {
            minVariantPrice { amount currencyCode }
          }
          featuredImage { url }
          images(first: 1) {
            edges {
              node { url }
            }
          }
        }
      }
      pageInfo { hasNextPage }
    }
  }`;

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; res = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(
    `https://${SHOPIFY_STORE}/admin/api/2026-04/graphql.json`,
    {
      method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
      headers: {
        &lt;span class="hl-green"&gt;'X-Shopify-Access-Token'&lt;/span&gt;: SHOPIFY_TOKEN,
        &lt;span class="hl-green"&gt;'Content-Type'&lt;/span&gt;: &lt;span class="hl-green"&gt;'application/json'&lt;/span&gt;,
      },
      body: JSON.stringify({ query }),
    }
  );

  &lt;span class="hl-blue"&gt;return&lt;/span&gt; res.json();
}

&lt;span class="hl-blue"&gt;async function&lt;/span&gt; insertImage(imageUrl, metadata) {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; res = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(
    `https://api.vecstore.app/api/databases/${VECSTORE_DB}/documents`,
    {
      method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
      headers: {
        &lt;span class="hl-green"&gt;'X-API-Key'&lt;/span&gt;: VECSTORE_KEY,
        &lt;span class="hl-green"&gt;'Content-Type'&lt;/span&gt;: &lt;span class="hl-green"&gt;'application/json'&lt;/span&gt;,
      },
      body: JSON.stringify({ image_url: imageUrl, metadata }),
    }
  );

  &lt;span class="hl-blue"&gt;return&lt;/span&gt; res.json();
}

&lt;span class="hl-blue"&gt;async function&lt;/span&gt; syncAll() {
  &lt;span class="hl-blue"&gt;let&lt;/span&gt; cursor = &lt;span class="hl-blue"&gt;null&lt;/span&gt;;
  &lt;span class="hl-blue"&gt;let&lt;/span&gt; count = 0;

  &lt;span class="hl-blue"&gt;while&lt;/span&gt; (&lt;span class="hl-blue"&gt;true&lt;/span&gt;) {
    &lt;span class="hl-blue"&gt;const&lt;/span&gt; data = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetchProducts(cursor);
    &lt;span class="hl-blue"&gt;const&lt;/span&gt; edges = data.data.products.edges;

    &lt;span class="hl-blue"&gt;for&lt;/span&gt; (&lt;span class="hl-blue"&gt;const&lt;/span&gt; { node, cursor: c } &lt;span class="hl-blue"&gt;of&lt;/span&gt; edges) {
      &lt;span class="hl-blue"&gt;const&lt;/span&gt; imageUrl = node.featuredImage?.url
        || node.images.edges[0]?.node.url;

      &lt;span class="hl-blue"&gt;if&lt;/span&gt; (!imageUrl) &lt;span class="hl-blue"&gt;continue&lt;/span&gt;;

      &lt;span class="hl-blue"&gt;await&lt;/span&gt; insertImage(imageUrl, {
        shopify_id: node.id,
        title: node.title,
        handle: node.handle,
        price: node.priceRangeV2.minVariantPrice.amount,
        currency: node.priceRangeV2.minVariantPrice.currencyCode,
        url: `https://${SHOPIFY_STORE}/products/${node.handle}`,
        image_url: imageUrl,
      });

      count++;
      console.log(`[${count}] ${node.title}`);
      cursor = c;
    }

    &lt;span class="hl-blue"&gt;if&lt;/span&gt; (!data.data.products.pageInfo.hasNextPage) &lt;span class="hl-blue"&gt;break&lt;/span&gt;;
  }

  console.log(`Done. Synced ${count} products.`);
}

syncAll();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Run it:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SHOPIFY_ACCESS_TOKEN=shpat_xxx VECSTORE_API_KEY=your_key VECSTORE_DB_ID=your_db &lt;span class="hl-blue"&gt;node&lt;/span&gt; sync-catalog.js&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This pages through your entire Shopify catalog using GraphQL cursor pagination, grabs the primary image for each product, and inserts it into Vecstore with metadata attached. The metadata is important because it comes back with every search result, so you can render product cards without a second Shopify API call.&lt;/p&gt;

&lt;p&gt;We're inserting one image per product (the featured image). If you insert all variant images, your "similar products" results will be flooded with color variants of the same item. One image per product keeps results useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Build the Search Backend
&lt;/h2&gt;

&lt;p&gt;You need a small backend to sit between your storefront and the Vecstore API. This keeps your API key off the client.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;server.js&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-blue"&gt;import&lt;/span&gt; express &lt;span class="hl-blue"&gt;from&lt;/span&gt; &lt;span class="hl-green"&gt;'express'&lt;/span&gt;;
&lt;span class="hl-blue"&gt;import&lt;/span&gt; cors &lt;span class="hl-blue"&gt;from&lt;/span&gt; &lt;span class="hl-green"&gt;'cors'&lt;/span&gt;;
&lt;span class="hl-blue"&gt;import&lt;/span&gt; multer &lt;span class="hl-blue"&gt;from&lt;/span&gt; &lt;span class="hl-green"&gt;'multer'&lt;/span&gt;;
&lt;span class="hl-blue"&gt;import&lt;/span&gt; fs &lt;span class="hl-blue"&gt;from&lt;/span&gt; &lt;span class="hl-green"&gt;'fs'&lt;/span&gt;;

&lt;span class="hl-blue"&gt;const&lt;/span&gt; app = express();
app.use(cors());
app.use(express.json());

&lt;span class="hl-blue"&gt;const&lt;/span&gt; upload = multer({ dest: &lt;span class="hl-green"&gt;'uploads/'&lt;/span&gt; });

&lt;span class="hl-blue"&gt;const&lt;/span&gt; API_KEY = process.env.VECSTORE_API_KEY;
&lt;span class="hl-blue"&gt;const&lt;/span&gt; DB_ID = process.env.VECSTORE_DB_ID;
&lt;span class="hl-blue"&gt;const&lt;/span&gt; BASE = &lt;span class="hl-green"&gt;'https://api.vecstore.app/api'&lt;/span&gt;;
&lt;span class="hl-blue"&gt;const&lt;/span&gt; HEADERS = {
  &lt;span class="hl-green"&gt;'X-API-Key'&lt;/span&gt;: API_KEY,
  &lt;span class="hl-green"&gt;'Content-Type'&lt;/span&gt;: &lt;span class="hl-green"&gt;'application/json'&lt;/span&gt;,
};

&lt;span class="hl-gray"&gt;// Text search - "red leather bag", "minimalist desk lamp"&lt;/span&gt;
app.post(&lt;span class="hl-green"&gt;'/api/search/text'&lt;/span&gt;, &lt;span class="hl-blue"&gt;async&lt;/span&gt; (req, res) =&amp;gt; {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; { query, top_k = 12 } = req.body;

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; result = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(`${BASE}/databases/${DB_ID}/search`, {
    method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
    headers: HEADERS,
    body: JSON.stringify({ query, top_k }),
  });

  res.json(&lt;span class="hl-blue"&gt;await&lt;/span&gt; result.json());
});

&lt;span class="hl-gray"&gt;// Image search - upload a photo, find similar products&lt;/span&gt;
app.post(&lt;span class="hl-green"&gt;'/api/search/image'&lt;/span&gt;, upload.single(&lt;span class="hl-green"&gt;'image'&lt;/span&gt;), &lt;span class="hl-blue"&gt;async&lt;/span&gt; (req, res) =&amp;gt; {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; base64 = fs.readFileSync(req.file.path, { encoding: &lt;span class="hl-green"&gt;'base64'&lt;/span&gt; });

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; result = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(`${BASE}/databases/${DB_ID}/search`, {
    method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
    headers: HEADERS,
    body: JSON.stringify({ image: base64, top_k: 12 }),
  });

  fs.unlinkSync(req.file.path);
  res.json(&lt;span class="hl-blue"&gt;await&lt;/span&gt; result.json());
});

&lt;span class="hl-gray"&gt;// Similar products - given a product image URL, find lookalikes&lt;/span&gt;
app.post(&lt;span class="hl-green"&gt;'/api/similar'&lt;/span&gt;, &lt;span class="hl-blue"&gt;async&lt;/span&gt; (req, res) =&amp;gt; {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; { image_url, exclude_handle, top_k = 6 } = req.body;

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; result = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(`${BASE}/databases/${DB_ID}/search`, {
    method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
    headers: HEADERS,
    body: JSON.stringify({ image_url, top_k: top_k + 1 }),
  });

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; data = &lt;span class="hl-blue"&gt;await&lt;/span&gt; result.json();

  &lt;span class="hl-gray"&gt;// filter out the current product&lt;/span&gt;
  data.results = (data.results || [])
    .filter(r =&amp;gt; r.metadata?.handle !== exclude_handle)
    .slice(0, top_k);

  res.json(data);
});

app.listen(3001, () =&amp;gt; console.log(&lt;span class="hl-green"&gt;'Running on 3001'&lt;/span&gt;));
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Three routes: text search for the search bar, image search for photo uploads, and a similar products endpoint that filters out the current product. The similar products endpoint takes the current product's handle so it can exclude it from results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Add Search to Your Storefront
&lt;/h2&gt;

&lt;p&gt;Now the frontend. There are two ways to get this onto your Shopify store: a theme app extension (the proper way) or a script tag (the quick way). We'll use a script tag because it works on any Shopify theme without building a full Shopify app.&lt;/p&gt;

&lt;p&gt;Create a JavaScript file and host it somewhere accessible (your backend, a CDN, wherever). This gets injected into your Shopify theme.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-gray"&gt;// shopify-search-widget.js&lt;/span&gt;

&lt;span class="hl-blue"&gt;const&lt;/span&gt; API_BASE = &lt;span class="hl-green"&gt;'https://your-backend.com'&lt;/span&gt;;

&lt;span class="hl-blue"&gt;function&lt;/span&gt; createSearchWidget() {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; container = document.createElement(&lt;span class="hl-green"&gt;'div'&lt;/span&gt;);
  container.id = &lt;span class="hl-green"&gt;'vs-search'&lt;/span&gt;;
  container.innerHTML = `
    &amp;lt;div id="vs-overlay" style="display:none; position:fixed; inset:0;
         background:rgba(0,0,0,0.5); z-index:9999;
         display:none; align-items:center; justify-content:center;"&amp;gt;
      &amp;lt;div style="background:white; border-radius:12px; padding:24px;
           width:90%; max-width:640px; max-height:80vh; overflow-y:auto;"&amp;gt;
        &amp;lt;div style="display:flex; gap:8px; margin-bottom:16px;"&amp;gt;
          &amp;lt;input id="vs-input" type="text"
            placeholder="Describe what you're looking for..."
            style="flex:1; padding:10px 14px; border:1px solid #ddd;
                   border-radius:8px; font-size:15px;" /&amp;gt;
          &amp;lt;label style="padding:10px 16px; border:1px solid #ddd;
                 border-radius:8px; cursor:pointer; font-size:14px;"&amp;gt;
            Upload photo
            &amp;lt;input id="vs-file" type="file" accept="image/*"
                   style="display:none;" /&amp;gt;
          &amp;lt;/label&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div id="vs-results" style="display:grid;
             grid-template-columns:repeat(auto-fill,minmax(140px,1fr));
             gap:12px;"&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  `;

  document.body.appendChild(container);

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; overlay = document.getElementById(&lt;span class="hl-green"&gt;'vs-overlay'&lt;/span&gt;);
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; input = document.getElementById(&lt;span class="hl-green"&gt;'vs-input'&lt;/span&gt;);
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; fileInput = document.getElementById(&lt;span class="hl-green"&gt;'vs-file'&lt;/span&gt;);
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; resultsDiv = document.getElementById(&lt;span class="hl-green"&gt;'vs-results'&lt;/span&gt;);

  &lt;span class="hl-gray"&gt;// close on overlay click&lt;/span&gt;
  overlay.addEventListener(&lt;span class="hl-green"&gt;'click'&lt;/span&gt;, (e) =&amp;gt; {
    &lt;span class="hl-blue"&gt;if&lt;/span&gt; (e.target === overlay) overlay.style.display = &lt;span class="hl-green"&gt;'none'&lt;/span&gt;;
  });

  &lt;span class="hl-gray"&gt;// text search on Enter&lt;/span&gt;
  &lt;span class="hl-blue"&gt;let&lt;/span&gt; timer;
  input.addEventListener(&lt;span class="hl-green"&gt;'keyup'&lt;/span&gt;, (e) =&amp;gt; {
    clearTimeout(timer);
    &lt;span class="hl-blue"&gt;if&lt;/span&gt; (e.key === &lt;span class="hl-green"&gt;'Enter'&lt;/span&gt;) {
      searchByText(input.value);
    }
  });

  &lt;span class="hl-gray"&gt;// image search on file upload&lt;/span&gt;
  fileInput.addEventListener(&lt;span class="hl-green"&gt;'change'&lt;/span&gt;, (e) =&amp;gt; {
    &lt;span class="hl-blue"&gt;const&lt;/span&gt; file = e.target.files[0];
    &lt;span class="hl-blue"&gt;if&lt;/span&gt; (file) searchByImage(file);
  });

  &lt;span class="hl-blue"&gt;async function&lt;/span&gt; searchByText(query) {
    &lt;span class="hl-blue"&gt;if&lt;/span&gt; (!query.trim()) &lt;span class="hl-blue"&gt;return&lt;/span&gt;;
    resultsDiv.innerHTML = &lt;span class="hl-green"&gt;'&amp;lt;p&amp;gt;Searching...&amp;lt;/p&amp;gt;'&lt;/span&gt;;
    &lt;span class="hl-blue"&gt;const&lt;/span&gt; res = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(`${API_BASE}/api/search/text`, {
      method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
      headers: { &lt;span class="hl-green"&gt;'Content-Type'&lt;/span&gt;: &lt;span class="hl-green"&gt;'application/json'&lt;/span&gt; },
      body: JSON.stringify({ query }),
    });
    renderResults(&lt;span class="hl-blue"&gt;await&lt;/span&gt; res.json());
  }

  &lt;span class="hl-blue"&gt;async function&lt;/span&gt; searchByImage(file) {
    resultsDiv.innerHTML = &lt;span class="hl-green"&gt;'&amp;lt;p&amp;gt;Searching...&amp;lt;/p&amp;gt;'&lt;/span&gt;;
    &lt;span class="hl-blue"&gt;const&lt;/span&gt; formData = &lt;span class="hl-blue"&gt;new&lt;/span&gt; FormData();
    formData.append(&lt;span class="hl-green"&gt;'image'&lt;/span&gt;, file);
    &lt;span class="hl-blue"&gt;const&lt;/span&gt; res = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(`${API_BASE}/api/search/image`, {
      method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
      body: formData,
    });
    renderResults(&lt;span class="hl-blue"&gt;await&lt;/span&gt; res.json());
  }

  &lt;span class="hl-blue"&gt;function&lt;/span&gt; renderResults(data) {
    &lt;span class="hl-blue"&gt;const&lt;/span&gt; results = data.results || [];
    &lt;span class="hl-blue"&gt;if&lt;/span&gt; (!results.length) {
      resultsDiv.innerHTML = &lt;span class="hl-green"&gt;'&amp;lt;p&amp;gt;No results found.&amp;lt;/p&amp;gt;'&lt;/span&gt;;
      &lt;span class="hl-blue"&gt;return&lt;/span&gt;;
    }
    resultsDiv.innerHTML = results.map(r =&amp;gt; `
      &amp;lt;a href="${r.metadata.url}" style="text-decoration:none; color:inherit;"&amp;gt;
        &amp;lt;img src="${r.metadata.image_url}" alt="${r.metadata.title}"
          style="width:100%; aspect-ratio:1; object-fit:cover;
                 border-radius:8px;" /&amp;gt;
        &amp;lt;p style="font-size:13px; margin:6px 0 2px;"&amp;gt;${r.metadata.title}&amp;lt;/p&amp;gt;
        &amp;lt;p style="font-size:13px; font-weight:600;"&amp;gt;
          ${r.metadata.currency} ${r.metadata.price}
        &amp;lt;/p&amp;gt;
      &amp;lt;/a&amp;gt;
    `).join(&lt;span class="hl-green"&gt;''&lt;/span&gt;);
  }

  &lt;span class="hl-blue"&gt;return&lt;/span&gt; { open: () =&amp;gt; { overlay.style.display = &lt;span class="hl-green"&gt;'flex'&lt;/span&gt;; input.focus(); } };
}

&lt;span class="hl-gray"&gt;// Initialize&lt;/span&gt;
&lt;span class="hl-blue"&gt;const&lt;/span&gt; searchWidget = createSearchWidget();

&lt;span class="hl-gray"&gt;// Hook into existing search icon/button on your theme&lt;/span&gt;
document.querySelectorAll(&lt;span class="hl-green"&gt;'[data-vs-trigger]'&lt;/span&gt;).forEach(el =&amp;gt; {
  el.addEventListener(&lt;span class="hl-green"&gt;'click'&lt;/span&gt;, (e) =&amp;gt; {
    e.preventDefault();
    searchWidget.open();
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To wire this up, add a &lt;code&gt;data-vs-trigger&lt;/code&gt; attribute to any element in your Shopify theme that should open the search modal. Could be the existing search icon, a new button, whatever. When clicked, the modal opens with text input and photo upload.&lt;/p&gt;

&lt;p&gt;In your Shopify theme, load the script by adding this to your &lt;code&gt;theme.liquid&lt;/code&gt; before &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script src="https://your-backend.com/shopify-search-widget.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;
  
  
  Step 4: Add Similar Products to Product Pages
&lt;/h2&gt;

&lt;p&gt;This is where the real money is. A "similar products" section on every product page that actually shows products that look alike, not just products from the same collection.&lt;/p&gt;

&lt;p&gt;Add this script to your product page template (or &lt;code&gt;theme.liquid&lt;/code&gt; if you want it everywhere):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-gray"&gt;// similar-products.js&lt;/span&gt;

&lt;span class="hl-blue"&gt;const&lt;/span&gt; API_BASE = &lt;span class="hl-green"&gt;'https://your-backend.com'&lt;/span&gt;;

&lt;span class="hl-blue"&gt;async function&lt;/span&gt; loadSimilarProducts() {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; container = document.getElementById(&lt;span class="hl-green"&gt;'vs-similar'&lt;/span&gt;);
  &lt;span class="hl-blue"&gt;if&lt;/span&gt; (!container) &lt;span class="hl-blue"&gt;return&lt;/span&gt;;

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; imageUrl = container.dataset.image;
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; handle = container.dataset.handle;

  &lt;span class="hl-blue"&gt;if&lt;/span&gt; (!imageUrl || !handle) &lt;span class="hl-blue"&gt;return&lt;/span&gt;;

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; res = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(`${API_BASE}/api/similar`, {
    method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
    headers: { &lt;span class="hl-green"&gt;'Content-Type'&lt;/span&gt;: &lt;span class="hl-green"&gt;'application/json'&lt;/span&gt; },
    body: JSON.stringify({
      image_url: imageUrl,
      exclude_handle: handle,
    }),
  });

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; data = &lt;span class="hl-blue"&gt;await&lt;/span&gt; res.json();
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; results = data.results || [];

  &lt;span class="hl-blue"&gt;if&lt;/span&gt; (!results.length) &lt;span class="hl-blue"&gt;return&lt;/span&gt;;

  container.innerHTML = `
    &amp;lt;h3 style="margin-bottom:16px;"&amp;gt;You might also like&amp;lt;/h3&amp;gt;
    &amp;lt;div style="display:grid;
         grid-template-columns:repeat(auto-fill,minmax(160px,1fr));
         gap:16px;"&amp;gt;
      ${results.map(r =&amp;gt; `
        &amp;lt;a href="${r.metadata.url}"
           style="text-decoration:none; color:inherit;"&amp;gt;
          &amp;lt;img src="${r.metadata.image_url}" alt="${r.metadata.title}"
            style="width:100%; aspect-ratio:1; object-fit:cover;
                   border-radius:8px;" /&amp;gt;
          &amp;lt;p style="font-size:13px; margin:8px 0 2px;"&amp;gt;
            ${r.metadata.title}
          &amp;lt;/p&amp;gt;
          &amp;lt;p style="font-size:13px; font-weight:600;"&amp;gt;
            ${r.metadata.currency} ${r.metadata.price}
          &amp;lt;/p&amp;gt;
        &amp;lt;/a&amp;gt;
      `).join('')}
    &amp;lt;/div&amp;gt;
  `;
}

loadSimilarProducts();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In your Shopify product template, add a container where you want the similar products to appear:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;div id="vs-similar"
  data-image="{{ product.featured_image | image_url: width: 800 }}"
  data-handle="{{ product.handle }}"&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;script src="https://your-backend.com/similar-products.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Shopify's Liquid template passes the product image URL and handle as data attributes. The script picks them up, calls your backend, and renders the results. If no similar products are found, nothing shows up. No empty sections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping Your Catalog in Sync
&lt;/h2&gt;

&lt;p&gt;Your Vecstore database needs to stay up to date when you add, update, or remove products in Shopify. Two approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Webhook-based (recommended).&lt;/strong&gt; Register Shopify webhooks for &lt;code&gt;products/create&lt;/code&gt;, &lt;code&gt;products/update&lt;/code&gt;, and &lt;code&gt;products/delete&lt;/code&gt;. When a product changes, your backend inserts, updates, or removes it from Vecstore automatically.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-gray"&gt;// handle product create/update webhook&lt;/span&gt;
app.post(&lt;span class="hl-green"&gt;'/webhooks/products'&lt;/span&gt;, &lt;span class="hl-blue"&gt;async&lt;/span&gt; (req, res) =&amp;gt; {
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; product = req.body;
  &lt;span class="hl-blue"&gt;const&lt;/span&gt; imageUrl = product.image?.src;

  &lt;span class="hl-blue"&gt;if&lt;/span&gt; (!imageUrl) &lt;span class="hl-blue"&gt;return&lt;/span&gt; res.sendStatus(200);

  &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(`${BASE}/databases/${DB_ID}/documents`, {
    method: &lt;span class="hl-green"&gt;'POST'&lt;/span&gt;,
    headers: HEADERS,
    body: JSON.stringify({
      image_url: imageUrl,
      metadata: {
        shopify_id: `gid://shopify/Product/${product.id}`,
        title: product.title,
        handle: product.handle,
        price: product.variants[0]?.price,
        url: `https://${SHOPIFY_STORE}/products/${product.handle}`,
        image_url: imageUrl,
      },
    }),
  });

  res.sendStatus(200);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Cron-based.&lt;/strong&gt; Run the sync script from Step 1 on a schedule (daily, hourly, whatever fits your update frequency). Simpler to set up, but there's a lag between product changes and search results updating.&lt;/p&gt;

&lt;p&gt;For most stores, webhooks are worth the extra setup. A customer shouldn't search for a product you added an hour ago and get nothing back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to Keep in Mind
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cache similar products.&lt;/strong&gt; The similar products for a given item don't change unless your catalog changes. Cache the results (even in a simple JSON file or Redis) so you're not hitting the API on every product page view. Refresh when your catalog syncs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Theme compatibility.&lt;/strong&gt; The script tag approach works on any Shopify theme. If you want tighter integration (custom blocks in the theme editor, settings panels), build a proper theme app extension instead. More work upfront, better experience for store owners.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image quality.&lt;/strong&gt; Shopify's image URLs support size parameters. Use &lt;code&gt;width: 800&lt;/code&gt; or similar when passing image URLs to Vecstore. Bigger doesn't improve search quality, it just slows things down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Variants.&lt;/strong&gt; Only index one image per product, not one per variant. If you sell a shirt in 5 colors and index all 5, searching for a blue shirt returns 4 other color variants of the same shirt. Not useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Costs.&lt;/strong&gt; A store with 5,000 products and 30,000 daily product page views: the initial sync costs 5,000 credits, and each similar products query costs 1 credit. With caching, you're looking at the sync cost plus a fraction of the page views. At $1.60 per 1,000 credits, the math works out to a few dollars per month after caching.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Else You Can Do
&lt;/h2&gt;

&lt;p&gt;Same database, same API key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Text search&lt;/strong&gt; that understands meaning, not just keywords. "Cozy winter sweater" matches chunky knits even if none are tagged that way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NSFW detection&lt;/strong&gt; if you accept user-uploaded images. Check them before they appear on your site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer photo search&lt;/strong&gt; for stores with user-generated content. A customer uploads a photo from their home and finds similar products in your catalog.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The full setup: a catalog sync script, an Express backend with three routes, and two frontend scripts. One for search, one for similar products. No ML models, no GPU servers, no vector database to manage.&lt;/p&gt;

&lt;p&gt;The hardest part is the initial catalog sync. After that, everything runs off a single API call per search query. And the similar products section is the kind of feature that directly moves revenue without requiring any ongoing manual work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;Get started with Vecstore&lt;/a&gt; - free tier includes enough credits to sync a small catalog and test search.&lt;/p&gt;

</description>
      <category>imagesearch</category>
      <category>industries</category>
      <category>tutorials</category>
    </item>
    <item>
      <title>Vecstore vs Elasticsearch: Managed Search API vs DIY Search Infrastructure</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Mon, 13 Apr 2026 17:27:26 +0000</pubDate>
      <link>https://dev.to/kencho/vecstore-vs-elasticsearch-managed-search-api-vs-diy-search-infrastructure-268k</link>
      <guid>https://dev.to/kencho/vecstore-vs-elasticsearch-managed-search-api-vs-diy-search-infrastructure-268k</guid>
      <description>&lt;p&gt;Elasticsearch gets recommended for almost everything that looks vaguely like search.&lt;/p&gt;

&lt;p&gt;Need site search? Elasticsearch. Need semantic search? Elasticsearch has vector search. Need hybrid search? Elasticsearch can do that too. Need something scalable? Also Elasticsearch.&lt;/p&gt;

&lt;p&gt;None of that is wrong. Elasticsearch is one of the most capable search engines ever built.&lt;/p&gt;

&lt;p&gt;But that recommendation usually skips the part that matters most: Elasticsearch is a search engine you build on top of. Vecstore is a search product you call.&lt;/p&gt;

&lt;p&gt;That difference sounds small until you're three weeks into mappings, analyzers, indexing pipelines, shard sizing, and relevance tuning. At that point you're no longer deciding between two APIs. You're deciding whether search is a feature in your product or a system your team now owns.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Elasticsearch Actually Is
&lt;/h2&gt;

&lt;p&gt;Elasticsearch is infrastructure.&lt;/p&gt;

&lt;p&gt;It's a distributed search engine and analytics system. It gives you indexes, mappings, analyzers, BM25 ranking, aggregations, filters, vector fields, kNN search, hybrid retrieval, ingest pipelines, and a huge amount of control over how search behaves.&lt;/p&gt;

&lt;p&gt;That flexibility is exactly why so many teams use it. If you want to control tokenization, ranking logic, field boosts, synonym handling, language analyzers, aggregations, and deployment shape, Elasticsearch gives you all of that.&lt;/p&gt;

&lt;p&gt;It also means you are assembling the search experience yourself.&lt;/p&gt;

&lt;p&gt;Even on Elastic Cloud, where the cluster management burden is lower, you're still making search-engine-level decisions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How documents are indexed&lt;/li&gt;
&lt;li&gt;Which fields get keyword search versus semantic search&lt;/li&gt;
&lt;li&gt;How lexical and vector results get combined&lt;/li&gt;
&lt;li&gt;Which analyzers and filters you need&lt;/li&gt;
&lt;li&gt;How mappings evolve when your schema changes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is excellent if you want control. It is expensive if you just want search to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Vecstore Actually Is
&lt;/h2&gt;

&lt;p&gt;Vecstore is a managed search API.&lt;/p&gt;

&lt;p&gt;You create a database, send in your text or images, and search them. Embeddings, indexing, multimodal retrieval, OCR search, face search, and NSFW detection are handled for you. You are not stitching together a search engine, a model provider, a queue, and a second storage layer just to return one search result.&lt;/p&gt;

&lt;p&gt;That changes the whole shape of the project.&lt;/p&gt;

&lt;p&gt;With Vecstore, search is an API integration. With Elasticsearch, search is usually a subsystem.&lt;/p&gt;

&lt;p&gt;That doesn't make Elasticsearch worse. It makes it a different category of tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Teams Underestimate Elasticsearch
&lt;/h2&gt;

&lt;p&gt;Most teams don't underestimate Elasticsearch because they think it is weak. They underestimate it because they think they are buying search, when they are really buying control plus responsibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Index design becomes product work.&lt;/strong&gt; Before search feels good to users, you have to decide what gets indexed, how fields are mapped, which analyzers are used, what should be boosted, what should be filterable, and how query parsing should behave. None of this is fake complexity. It is real work that somebody has to own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema changes are not casual.&lt;/strong&gt; In a normal application database, adding a field is usually boring. In Elasticsearch, changes to mappings can force reindexing. On a large corpus, that is not a small detail. It affects rollout plans, backfills, and operational risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relevance tuning never really ends.&lt;/strong&gt; Search quality in Elasticsearch is rarely bad because the engine is incapable. It is bad because ranking requires iteration. You tune boosts, analyzers, synonyms, typo behavior, field weights, filters, and sometimes custom scoring. This is where a lot of time goes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic search adds another layer, not less work.&lt;/strong&gt; Elasticsearch absolutely supports vector search and hybrid search. But once you move beyond classic keyword search, the number of decisions increases. You now care about chunking, embeddings, vector fields, retrieval strategy, score blending, latency tradeoffs, and model quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Someone becomes the Elasticsearch person.&lt;/strong&gt; It might be a backend engineer, a platform engineer, or the founder at midnight. But once Elasticsearch matters to the product, somebody owns cluster health, indexing performance, search regressions, and operational surprises.&lt;/p&gt;

&lt;p&gt;This is normal for infrastructure. It is just very different from consuming a finished API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantic Search in Elasticsearch Is Real. It Is Still Elasticsearch.
&lt;/h2&gt;

&lt;p&gt;This is where comparison posts often get sloppy, so it's worth being precise.&lt;/p&gt;

&lt;p&gt;Elasticsearch is not "just keyword search" anymore. Elastic supports dense vectors, kNN retrieval, hybrid search, and semantic features. If you want to build modern retrieval on Elastic, you can.&lt;/p&gt;

&lt;p&gt;The question is not whether it can be done. The question is how much of the system you want to own.&lt;/p&gt;

&lt;p&gt;If your team likes controlling every layer, Elasticsearch is compelling. You can shape the ranking stack exactly how you want. You can combine lexical signals, metadata filters, behavioral features, recency, vector scores, and business logic into one retrieval pipeline.&lt;/p&gt;

&lt;p&gt;If your team does not want that job, the same flexibility becomes drag.&lt;/p&gt;

&lt;p&gt;Vecstore takes the opposite position. You do not manage embeddings. You do not design vector schemas. You do not wire together lexical and semantic retrieval manually. You do not spend time deciding which model should be attached to which field. You send the data in and search it.&lt;/p&gt;

&lt;p&gt;That tradeoff is the whole product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Image Search Gap Is Bigger Than It Looks
&lt;/h2&gt;

&lt;p&gt;This is the part where the difference between the two products becomes obvious.&lt;/p&gt;

&lt;p&gt;Elasticsearch can store vectors for images if you generate those vectors yourself. That means image search is possible. But nothing about the workflow is native. You need the model, the embedding pipeline, the indexing logic, the metadata strategy, and the query flow.&lt;/p&gt;

&lt;p&gt;If you need reverse image search, text-to-image search, OCR search, face search, or content moderation, Elasticsearch is not giving you a finished feature. It is giving you a place to store and query whatever representation you build.&lt;/p&gt;

&lt;p&gt;Vecstore already behaves like an image search product. Upload an image, search by image, search by text, search the text inside the image, moderate the image, or find matching faces. The infrastructure question is already answered.&lt;/p&gt;

&lt;p&gt;For teams building visual search, that difference is not academic. It is months of engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Elasticsearch Is the Right Choice
&lt;/h2&gt;

&lt;p&gt;Elasticsearch is the right choice more often than some comparison pages admit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You already run Elastic.&lt;/strong&gt; If your team already has Elastic in production for logs, observability, or existing site search, extending that investment may be rational. You already understand the operational model, and adding one more index is not the same as introducing a brand new system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need heavy filtering and aggregations.&lt;/strong&gt; Elasticsearch is excellent when search is tightly tied to faceting, nested filters, aggregations, and structured exploration. Product catalogs, analytics-heavy UIs, and enterprise search interfaces often need this level of control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You want maximum ranking control.&lt;/strong&gt; If you care deeply about analyzers, tokenization, field-level relevance, custom scoring scripts, and ranking experimentation, Elasticsearch gives you room to build exactly what you want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your primary use case is not just product search.&lt;/strong&gt; Elastic shines in observability, security analytics, log search, and other workloads that Vecstore is not trying to replace. If search is only one part of a broader Elastic footprint, the decision changes.&lt;/p&gt;

&lt;p&gt;Elasticsearch is powerful because it is broad. That breadth is real value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Vecstore Is the Better Fit
&lt;/h2&gt;

&lt;p&gt;Vecstore is the better fit when you do not want search to become a platform project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need semantic search without building the stack around it.&lt;/strong&gt; Users should be able to type what they mean and get useful results back. Not after a month of tuning. Immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need image search as an actual feature, not a research project.&lt;/strong&gt; Reverse image search, text-to-image search, OCR search, face search, and moderation are all things teams ask for once they start handling media. On Elasticsearch, each of those starts with "first, let's build a pipeline." On Vecstore, they start with an API call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You do not have a dedicated search team.&lt;/strong&gt; Most startups and product teams do not have spare engineering capacity for search infrastructure. They have product work to ship. In that environment, "finished API" is not a luxury. It is the practical choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You want one system, not a chain of systems.&lt;/strong&gt; Elasticsearch-based semantic search often means combining your application database, Elastic, and one or more model or ingestion services. Vecstore collapses that stack.&lt;/p&gt;

&lt;p&gt;The value proposition is not philosophical. It is less surface area, fewer moving parts, and faster time to a working feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side-by-Side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Vecstore&lt;/th&gt;
&lt;th&gt;Elasticsearch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it is&lt;/td&gt;
&lt;td&gt;Managed search API&lt;/td&gt;
&lt;td&gt;Search engine and analytics platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main experience&lt;/td&gt;
&lt;td&gt;Send data, search it&lt;/td&gt;
&lt;td&gt;Design, tune, and run a search system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyword search&lt;/td&gt;
&lt;td&gt;Built in as part of hybrid search&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic search&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;td&gt;Supported, but configurable and infrastructure-heavy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid search&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;td&gt;Supported, but you decide how to combine signals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image search&lt;/td&gt;
&lt;td&gt;Native workflows&lt;/td&gt;
&lt;td&gt;Possible with your own models and pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OCR search&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;td&gt;Build it yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Face search&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;td&gt;Build it yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content moderation&lt;/td&gt;
&lt;td&gt;Built in&lt;/td&gt;
&lt;td&gt;Separate service required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multilingual&lt;/td&gt;
&lt;td&gt;Works out of the box&lt;/td&gt;
&lt;td&gt;Depends on analyzers, models, and setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relevance tuning&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Ongoing responsibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure overhead&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium to high, depending on setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Teams that want search shipped fast&lt;/td&gt;
&lt;td&gt;Teams that want control and already know Elastic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Real Decision
&lt;/h2&gt;

&lt;p&gt;The easiest way to think about this is not "which one is more powerful?"&lt;/p&gt;

&lt;p&gt;Elasticsearch is more powerful in the raw sense. It gives you more knobs, more architecture choices, more ways to shape retrieval, and more ways to integrate search into a larger infrastructure stack.&lt;/p&gt;

&lt;p&gt;The more useful question is this: do you want control badly enough to own the consequences of that control?&lt;/p&gt;

&lt;p&gt;If the answer is yes, Elasticsearch is a serious option.&lt;/p&gt;

&lt;p&gt;If the answer is no, and what you actually need is semantic search, image search, multilingual retrieval, or moderation to work inside your product without turning into a platform effort, Vecstore is the better fit.&lt;/p&gt;

&lt;p&gt;One is a search engine.&lt;/p&gt;

&lt;p&gt;The other is search, already turned into a product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vecstore.app/compare/vecstore-vs-elasticsearch" rel="noopener noreferrer"&gt;See the full comparison&lt;/a&gt; or &lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;try Vecstore free&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>comparisons</category>
      <category>semanticsearch</category>
    </item>
    <item>
      <title>You Don't Need a Vector Database</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Sun, 12 Apr 2026 15:17:55 +0000</pubDate>
      <link>https://dev.to/kencho/you-dont-need-a-vector-database-14ln</link>
      <guid>https://dev.to/kencho/you-dont-need-a-vector-database-14ln</guid>
      <description>&lt;p&gt;Somewhere in the last two years, "we need a vector database" became the default answer to every search problem. Team wants better product search? Vector database. Building a recommendation engine? Vector database. Need to search images? Vector database.&lt;/p&gt;

&lt;p&gt;The reasoning usually goes like this: traditional keyword search isn't good enough, semantic search uses vectors, therefore we need a vector database. It sounds logical. But it skips a pretty important question.&lt;/p&gt;

&lt;p&gt;Do you actually need a &lt;em&gt;database&lt;/em&gt;, or do you just need search that understands what your users mean?&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Vector Database Actually Is
&lt;/h2&gt;

&lt;p&gt;A vector database stores and indexes vectors (arrays of numbers that represent the meaning of text, images, or other data). You give it vectors, it stores them, and when you query it with another vector, it finds the most similar ones.&lt;/p&gt;

&lt;p&gt;That's it. That's the whole product.&lt;/p&gt;

&lt;p&gt;It doesn't generate those vectors for you. It doesn't understand your data. It doesn't know what "affordable hiking boots" means. It just stores numbers and does math to find which stored numbers are closest to your query numbers.&lt;/p&gt;

&lt;p&gt;To make a vector database useful, you need to build everything around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An embedding pipeline that converts your data into vectors&lt;/li&gt;
&lt;li&gt;A way to keep vectors in sync when your source data changes&lt;/li&gt;
&lt;li&gt;A separate database for your actual data (a vector database stores vectors and limited metadata, not your full records)&lt;/li&gt;
&lt;li&gt;Query resolution logic that takes vector IDs back to your primary database&lt;/li&gt;
&lt;li&gt;Model selection, tuning, and eventual migration when better models come out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A vector database is a storage layer. It's an important component in certain architectures. But it's a component, not a solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Starting From Infrastructure
&lt;/h2&gt;

&lt;p&gt;When you start with "we need a vector database," you're starting from infrastructure and working backwards toward the product. That's backwards.&lt;/p&gt;

&lt;p&gt;Here's what usually happens. A team decides they need better search. They research vector databases. They pick one. They spend two weeks setting it up, choosing an embedding model, building the ingestion pipeline, and writing the sync logic. They get a prototype working. The results are okay but not great. They realize they need to try a different embedding model. They re-embed everything. The results are better. Then they discover their sync pipeline has a bug and 15% of their vectors are stale. They fix it. A month has passed and they have... search that mostly works.&lt;/p&gt;

&lt;p&gt;Compare that to: call a search API, get results. Done in an afternoon.&lt;/p&gt;

&lt;p&gt;The vector database approach isn't wrong. It's just overkill for what most teams actually need. The majority of developers searching for "vector database" don't want to operate a vector database. They want their search to understand natural language. Those are very different things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Actually Needs a Vector Database
&lt;/h2&gt;

&lt;p&gt;There are legitimate use cases where a raw vector database is the right call. They all share a common pattern: the team needs control over the vector layer specifically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ML teams building custom retrieval systems.&lt;/strong&gt; If you have ML engineers who need to experiment with different embedding models, fine-tune them on your domain data, and control how vectors are generated and stored, a vector database is the right component. You're building a custom system and you need a storage layer that fits into it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RAG pipelines with specific requirements.&lt;/strong&gt; If you're building retrieval-augmented generation for an LLM and you need control over chunking strategies, embedding dimensions, retrieval scoring, and re-ranking, the flexibility of a raw vector index matters. You have opinions about every layer of the stack and you want to control each one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Research and experimentation.&lt;/strong&gt; If you're benchmarking embedding models, testing different similarity metrics, or building something novel, you want direct access to the vector operations. You're not building a product. You're building the thing that goes inside a product.&lt;/p&gt;

&lt;p&gt;The common thread: these teams have ML expertise and they want a component, not a finished product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Doesn't Need One (Most People)
&lt;/h2&gt;

&lt;p&gt;If you're building any of the following, you almost certainly don't need a vector database:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product search.&lt;/strong&gt; Your users type "warm jacket for camping" and you want to show insulated outdoor jackets even if no product has those exact words. You need semantic search, not a vector database. The difference: semantic search is the result you want. A vector database is one possible way to build it, and the most complicated one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content discovery.&lt;/strong&gt; Your blog, documentation, or knowledge base needs search that understands questions, not just keywords. "How do I reset my password" should match a help article titled "Account recovery steps." Again, this is a search quality problem, not an infrastructure problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image search.&lt;/strong&gt; Your users want to search by uploading a photo, describing what they're looking for, or finding text inside images. Building this on a vector database means bringing your own CLIP model, running inference, building an ingestion pipeline, and maintaining it all. Or you could use a search API that handles images natively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multilingual search.&lt;/strong&gt; Your users search in Japanese, Arabic, German, Spanish. With a vector database, the quality of multilingual search depends entirely on which embedding model you chose and how well it handles each language. That's a bet you're making without easy visibility into the results. With a purpose-built search API, multilingual support is handled internally and tested across languages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Any search feature where you just need it to work.&lt;/strong&gt; If search is a feature in your product rather than the core product itself, spending weeks on vector infrastructure is time that doesn't go toward your actual product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Build Trap
&lt;/h2&gt;

&lt;p&gt;There's a specific trap that developer teams fall into with vector databases, and it's worth calling out directly.&lt;/p&gt;

&lt;p&gt;Vector databases feel like building. You're setting up infrastructure, writing pipelines, choosing models, tuning parameters. It feels productive. It feels like engineering. And developers like building things.&lt;/p&gt;

&lt;p&gt;But the question isn't "can we build this?" It's "should we?"&lt;/p&gt;

&lt;p&gt;Building a search stack from a vector database is like building a car from an engine. Yes, the engine is the hard part. But you still need the transmission, the frame, the wheels, the steering, and a few thousand other things before anyone can drive it. The engine alone doesn't get you anywhere.&lt;/p&gt;

&lt;p&gt;A vector database is the engine. The embedding pipeline, sync layer, query resolution, model management, and operational monitoring are everything else. Some teams enjoy building all of that. Most teams would rather just have a car.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Alternative Looks Like
&lt;/h2&gt;

&lt;p&gt;Instead of assembling search from components, you can use a search API that handles the entire stack.&lt;/p&gt;

&lt;p&gt;With Vecstore, the workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a database&lt;/li&gt;
&lt;li&gt;Insert your data (text, images, or both)&lt;/li&gt;
&lt;li&gt;Call the search endpoint&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's no embedding model to choose. No vectors to generate or sync. No separate database for your source data. No pipeline to build or maintain. You send in your data and search it. Vecstore handles embedding generation, indexing, retrieval, and ranking internally.&lt;/p&gt;

&lt;p&gt;This also means you're not locked to a specific embedding model. When better models come out, Vecstore upgrades internally. You don't re-embed millions of records. You don't even know it happened. Your search just gets better.&lt;/p&gt;

&lt;p&gt;And because everything runs through one API, you get text search, image search (reverse image, text-to-image, face search, OCR), multilingual search across 100+ languages, and NSFW detection across 52 categories. All from the same endpoint, the same database, the same API key.&lt;/p&gt;

&lt;p&gt;Try getting all of that from a vector database. You'd need a vector DB, an embedding API, a CLIP model, an OCR service, an NSFW detection service, and a primary database to hold your actual data. Six services, six bills, six things that can break.&lt;/p&gt;

&lt;h2&gt;
  
  
  "But What About Vendor Lock-in?"
&lt;/h2&gt;

&lt;p&gt;Fair concern. Using any managed service means depending on that service. But consider what lock-in actually looks like with each approach.&lt;/p&gt;

&lt;p&gt;With a vector database, your lock-in is deep. Your data lives in your primary database, your vectors live in the vector DB, and your embedding pipeline glues them together. If you want to switch vector databases, you need to re-embed everything and rebuild the integration. If you want to switch embedding models, you need to re-embed everything. Your architecture is coupled to three different services.&lt;/p&gt;

&lt;p&gt;With a search API like Vecstore, your data and search live in one place. If you ever want to leave, you export your data and point your API calls at a different service. One integration to replace, not three.&lt;/p&gt;

&lt;p&gt;Neither option is as portable as self-hosted open source. If zero vendor dependency is your top priority, look at Qdrant or Milvus and be prepared to operate them. But if you're choosing between managed services, the simpler architecture is actually easier to migrate away from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Decision
&lt;/h2&gt;

&lt;p&gt;Here's a simple framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose a vector database if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have ML engineers who need control over embedding models&lt;/li&gt;
&lt;li&gt;You're building a custom retrieval pipeline for an LLM&lt;/li&gt;
&lt;li&gt;You need to experiment with different models and similarity metrics&lt;/li&gt;
&lt;li&gt;Vector operations are a core part of your product's value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choose a search API if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need search to work in your product, but search isn't the product&lt;/li&gt;
&lt;li&gt;You don't have ML engineers (or your ML engineers have better things to do)&lt;/li&gt;
&lt;li&gt;You want text, image, and multilingual search without managing separate systems&lt;/li&gt;
&lt;li&gt;Time to launch matters more than customization of the vector layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most teams fall into the second category. They don't need a vector database. They need search that works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;Try Vecstore free&lt;/a&gt; or &lt;a href="https://vecstore.app/docs" rel="noopener noreferrer"&gt;explore the API docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>semanticsearch</category>
    </item>
    <item>
      <title>What It Costs to Search 1M Images in Production</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Sun, 12 Apr 2026 15:17:18 +0000</pubDate>
      <link>https://dev.to/kencho/what-it-costs-to-search-1m-images-in-production-4k09</link>
      <guid>https://dev.to/kencho/what-it-costs-to-search-1m-images-in-production-4k09</guid>
      <description>&lt;p&gt;Image search looks simple until you start building it. It's actually five or six separate infrastructure problems, each with its own monthly bill. This post prices out every piece for a real production workload: 1M images, thousands of users, enterprise-level traffic. One person can build it, but you should know what the bill looks like first.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Image Search Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;At insert time:&lt;/strong&gt; your backend receives an image, stores it in S3, runs it through a vision model (CLIP, OpenCLIP, SigLIP) to get a vector (an array of 768-1024 numbers representing what the image "means"), and stores that vector in a vector database alongside metadata.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At search time:&lt;/strong&gt; the user's query (text or image) gets embedded by the same model, sent to the vector database, which returns the closest matching vector IDs. Your backend resolves those IDs to actual image URLs and returns results.&lt;/p&gt;

&lt;p&gt;That's the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: GPU Inference
&lt;/h2&gt;

&lt;p&gt;This is where most of the money goes. You need a GPU running a vision model to convert images into vectors, both at insert time and search time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing a Model
&lt;/h3&gt;

&lt;p&gt;The main options are CLIP ViT-B/32 (fast, lower quality), CLIP ViT-L/14 (solid middle ground), OpenCLIP ViT-H/14 (best open-source quality), and SigLIP SO400M (newest, highest accuracy, slower).&lt;/p&gt;

&lt;p&gt;Most people go with &lt;strong&gt;OpenCLIP ViT-H/14&lt;/strong&gt;: 1024-dimensional vectors, 50-100 img/s on an A10G GPU. We'll use that for all calculations.&lt;/p&gt;

&lt;h3&gt;
  
  
  GPU Instances (AWS)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instance&lt;/th&gt;
&lt;th&gt;GPU&lt;/th&gt;
&lt;th&gt;Hourly&lt;/th&gt;
&lt;th&gt;Monthly&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;g6.xlarge&lt;/td&gt;
&lt;td&gt;1x L4 (24GB)&lt;/td&gt;
&lt;td&gt;$0.81&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$588&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;g5.xlarge&lt;/td&gt;
&lt;td&gt;1x A10G (24GB)&lt;/td&gt;
&lt;td&gt;$1.01&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$734&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;g5.2xlarge&lt;/td&gt;
&lt;td&gt;1x A10G (24GB)&lt;/td&gt;
&lt;td&gt;$1.21&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$885&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p3.2xlarge&lt;/td&gt;
&lt;td&gt;1x V100 (16GB)&lt;/td&gt;
&lt;td&gt;$3.06&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$2,234&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;g6.xlarge&lt;/strong&gt; is the best value. Runs OpenCLIP ViT-H/14 at 50-100 images per second.&lt;/p&gt;

&lt;h3&gt;
  
  
  CPU? No.
&lt;/h3&gt;

&lt;p&gt;OpenCLIP ViT-H/14 on CPU: 0.2-0.5 images per second. One image every 2-5 seconds. With 10 concurrent searches, users wait 20-50 seconds each. Embedding 1M images would take 23-58 days on CPU vs 3-6 hours on GPU.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/images/blog/1m-cost/instance-speed.webp" class="article-body-image-wrapper"&gt;&lt;img src="/assets/images/blog/1m-cost/instance-speed.webp" alt="Inference speed: CPU vs GPU"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Spot Instances
&lt;/h3&gt;

&lt;p&gt;Spot cuts GPU costs by 60-70% (g6.xlarge drops to ~$175-235/month). Great for batch embedding. Risky for live search since AWS can terminate with 2 minutes notice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency and Enterprise Traffic
&lt;/h3&gt;

&lt;p&gt;50-100 img/s on a single GPU sounds like plenty if you're thinking about a side project. It's not plenty at enterprise scale.&lt;/p&gt;

&lt;p&gt;Say your app has 50,000 daily active users, each doing a few searches. That's maybe 200,000-500,000 searches per day. Spread evenly, that's 3-6 queries per second. One GPU handles that fine.&lt;/p&gt;

&lt;p&gt;But traffic is never spread evenly. During peak hours (maybe 2-3 hours per day), you might see 20-50 queries per second. If you also have sellers or users uploading new images during those same hours, uploads and searches compete for the same GPU. A spike in uploads tanks your search latency.&lt;/p&gt;

&lt;p&gt;At this scale, you realistically need 2 GPU instances: one dedicated to serving search queries, one for processing new uploads and batch work. During peak traffic, you might even want a third.&lt;/p&gt;

&lt;p&gt;And this is just 1M images. Enterprise catalogs at 10M+ images with global traffic need even more. The GPU bill scales linearly with query volume.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Low traffic&lt;/td&gt;
&lt;td&gt;1x g6.xlarge&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$588&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium traffic&lt;/td&gt;
&lt;td&gt;2x g6.xlarge&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,176&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High traffic&lt;/td&gt;
&lt;td&gt;3x g6.xlarge&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,764&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch processing (spot)&lt;/td&gt;
&lt;td&gt;1x g6.xlarge spot&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+$175-235&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Part 2: Vector Storage
&lt;/h2&gt;

&lt;p&gt;1M vectors x 1024 dimensions x 4 bytes = &lt;strong&gt;4.1 GB&lt;/strong&gt; raw. With metadata and indexes, roughly 5-10 GB.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/images/blog/1m-cost/vector-db-cost.webp" class="article-body-image-wrapper"&gt;&lt;img src="/assets/images/blog/1m-cost/vector-db-cost.webp" alt="Vector database cost comparison"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These prices are for 1M vectors. At 10M vectors, the storage costs go up but the real pain is query latency. HNSW indexes get slower as they grow, and you'll likely need to bump your instance sizes or shard across multiple databases. That's when Pinecone and Qdrant start earning their price because they handle that scaling for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Image Storage and Delivery
&lt;/h2&gt;

&lt;p&gt;1M images at ~500KB each = 500 GB.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S3 (500 GB)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$11.50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudFront CDN&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$0-15&lt;/strong&gt; (1 TB free tier is permanent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$11.50-26.50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;S3 is cheap. This isn't the part of the bill that surprises you. Though at enterprise scale with heavy traffic, CloudFront costs can climb. If you're serving 5 TB/month in images, that's ~$425/month in CDN transfer alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: Backend
&lt;/h2&gt;

&lt;p&gt;The backend mostly routes requests between the GPU and the vector database. It doesn't need to be beefy.&lt;/p&gt;

&lt;p&gt;We went with Rust for ours because the memory efficiency and concurrency model means fewer instances. A single Rust server saturates its network before it runs out of CPU. The tradeoff is development speed, but for a service that's mostly routing requests, it's worth it. Most people use Python with FastAPI here, which works fine too. One developer can set either of these up in a day or two.&lt;/p&gt;

&lt;p&gt;A t3.small ($15/month) with auto scaling handles this. Set minimum 2 instances behind an ALB so you're never down during deployments, and let it scale up during traffic spikes.&lt;/p&gt;

&lt;p&gt;For enterprise traffic (thousands of concurrent users), you'll see the auto scaling group regularly running 4-6 instances during peak. Budget accordingly.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Instances&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Low traffic&lt;/td&gt;
&lt;td&gt;2x t3.small + ALB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$57&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium traffic&lt;/td&gt;
&lt;td&gt;4x avg + ALB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$90&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;6x avg + ALB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$120&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Part 5: Embedding 1M Images
&lt;/h2&gt;

&lt;p&gt;Before search works, you embed every image. 1M images through OpenCLIP ViT-H/14 on a g6.xlarge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time: ~3.7 hours&lt;/li&gt;
&lt;li&gt;Cost: ~$3.00 (under $1 on spot)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The compute is cheap. The bottleneck is I/O. If you process images one at a time, the GPU sits idle waiting for downloads between each inference.&lt;/p&gt;

&lt;p&gt;Python with &lt;code&gt;asyncio&lt;/code&gt; and &lt;code&gt;aiohttp&lt;/code&gt; solves this. Download images in batches of 100-200 concurrently, feed each batch to the GPU, batch-insert vectors into your database. This keeps the GPU busy. Without async batching, the same 3.7-hour job takes 12+ hours.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/images/blog/1m-cost/time-to-embed.webp" class="article-body-image-wrapper"&gt;&lt;img src="/assets/images/blog/1m-cost/time-to-embed.webp" alt="Time to embed 1M images"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Good weekend project. Set up the async pipeline, kick off the batch, go do something else while it runs. Just don't run it through your production backend. Spin up a dedicated instance for the initial load.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full Bill
&lt;/h2&gt;

&lt;p&gt;Two scenarios: a moderate-traffic app and an enterprise workload.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/images/blog/1m-cost/monthly-cost.webp" class="article-body-image-wrapper"&gt;&lt;img src="/assets/images/blog/1m-cost/monthly-cost.webp" alt="Monthly cost breakdown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$740/month&lt;/strong&gt; for moderate traffic (~100K searches/day). One GPU instance, Pinecone for vectors, small auto-scaling backend. The GPU eats 80% of the budget.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/images/blog/1m-cost/moderate-vs-enterprise.webp" class="article-body-image-wrapper"&gt;&lt;img src="/assets/images/blog/1m-cost/moderate-vs-enterprise.webp" alt="Moderate vs enterprise traffic costs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At enterprise scale (~500K+ searches/day), the bill jumps to &lt;strong&gt;$1,845/month&lt;/strong&gt;. The GPU cost more than doubles (you need 2 instances plus a spot instance for batch work), and you're adding monitoring, more CDN bandwidth, and a beefier backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Not Included
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Your time.&lt;/strong&gt; This is totally buildable by one developer. The pipeline, the GPU setup, the vector database, the backend, all of it. Expect 2-4 weeks for the initial build if you're doing it solo. The ongoing maintenance is the real time sink: model upgrades mean re-embedding everything, traffic spikes mean debugging capacity issues, and things will break at inconvenient times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling beyond 1M.&lt;/strong&gt; At 10M images, you're looking at roughly 3-5x these costs. GPU inference scales linearly, vector database costs go up, and the backend needs more capacity. The architecture stays the same but everything gets bigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is It Worth It?
&lt;/h2&gt;

&lt;p&gt;$740/month for moderate traffic is very doable, even for a solo developer. You can build this over a few weekends, it's well-documented technology, and the architecture is straightforward.&lt;/p&gt;

&lt;p&gt;At enterprise scale ($1,845/month and up), it's still reasonable if image search is a core part of your product. That's less than the cost of one engineer's monthly salary, and you're getting a real production system.&lt;/p&gt;

&lt;p&gt;Where it starts to hurt is the time. Not the building, the maintaining. Every CLIP model update, every scaling event, every outage. That's the ongoing tax. Whether you're fine paying that tax depends on how central image search is to what you're building.&lt;/p&gt;

&lt;p&gt;Either way, now you know what the bill looks like.&lt;/p&gt;

</description>
      <category>imagesearch</category>
    </item>
    <item>
      <title>What Is a Vector Database (And Do You Actually Need One)?</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Sun, 12 Apr 2026 15:16:42 +0000</pubDate>
      <link>https://dev.to/kencho/what-is-a-vector-database-and-do-you-actually-need-one-3mfj</link>
      <guid>https://dev.to/kencho/what-is-a-vector-database-and-do-you-actually-need-one-3mfj</guid>
      <description>&lt;p&gt;Every few years, a new type of database shows up and suddenly it's "the answer" to everything. Graph databases had their moment. Time-series databases had theirs. Right now, vector databases are in the spotlight—and unlike some hype cycles, this one is grounded in a real shift in how applications handle data.&lt;/p&gt;

&lt;p&gt;But "what is a vector database" is a surprisingly loaded question. The short answer: it's a database optimized for storing and searching high-dimensional vectors. The longer answer involves understanding what vectors are, why traditional databases can't handle them well, and—critically—whether you actually need one for your use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vectors and Embeddings: What They Actually Are
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;vector embedding&lt;/strong&gt; is a list of numbers that represents the meaning of a piece of data. Text, images, audio—any unstructured data can be converted into an embedding using a neural network (an embedding model).&lt;/p&gt;

&lt;p&gt;Take the sentence "how to train a puppy." An embedding model might convert that into a vector of 768 floating-point numbers. The sentence "tips for teaching a young dog" would produce a &lt;em&gt;different&lt;/em&gt; list of numbers, but one that's geometrically close to the first—because the meanings are similar.&lt;/p&gt;

&lt;p&gt;This is the key insight: &lt;strong&gt;similar meanings produce similar vectors.&lt;/strong&gt; The distance between two vectors in this high-dimensional space reflects how related their source data is. A sentence about puppy training and a sentence about dog obedience land near each other. A sentence about tax law lands far away.&lt;/p&gt;

&lt;p&gt;The same principle applies to images. A photo of a golden retriever and a photo of a labrador produce embeddings that are close together. A photo of a skyscraper does not.&lt;/p&gt;

&lt;p&gt;These vectors typically have 256 to 1,536 dimensions, depending on the model. That's where things get computationally interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Vector Database Does Differently
&lt;/h2&gt;

&lt;p&gt;A traditional relational database is built for exact matches. You query for rows where a column equals a value, falls within a range, or matches a pattern. The data structures behind this—B-trees, hash indexes—are optimized for precise lookups.&lt;/p&gt;

&lt;p&gt;Vector databases solve a fundamentally different problem: &lt;strong&gt;vector similarity search.&lt;/strong&gt; Given a query vector, find the most similar vectors in a collection of millions or billions. This isn't an exact match—it's a nearest-neighbor search in high-dimensional space.&lt;/p&gt;

&lt;p&gt;Why can't you just use PostgreSQL with a vector column and calculate cosine similarity? You can, for small datasets. But brute-force comparison against every vector in a table scales linearly. At 10 million vectors with 768 dimensions each, you're comparing against ~7.6 billion floating-point numbers per query. That's seconds, not milliseconds.&lt;/p&gt;

&lt;p&gt;Vector databases use &lt;strong&gt;approximate nearest neighbor (ANN)&lt;/strong&gt; algorithms to make this tractable. They trade a small amount of accuracy—maybe returning the 95th-percentile best match instead of the absolute best—for orders-of-magnitude speed improvements. A well-tuned ANN index can search through 100 million vectors in under 10 milliseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Vector Search Works, Step by Step
&lt;/h2&gt;

&lt;p&gt;Here's the full pipeline, with a vector database explained as a sequence of operations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Embed your data.&lt;/strong&gt; Run each document, image, or data point through an embedding model. This produces a vector for each item. For a catalog of 1 million products, you'd generate 1 million vectors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Index the vectors.&lt;/strong&gt; The vector database ingests these vectors and builds an index—a data structure that organizes them for fast similarity lookup. This is the expensive step. Depending on the algorithm, indexing 1 million 768-dimensional vectors might take minutes to hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Query.&lt;/strong&gt; When a user searches, their query is embedded using the same model, producing a query vector. The database searches its index for the nearest neighbors to that query vector.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Rank and return.&lt;/strong&gt; The database returns the top-K most similar vectors, along with their similarity scores and any metadata you stored alongside them. Your application uses these results to show search results, recommendations, or whatever the use case requires.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Indexing Algorithms Make It Fast
&lt;/h2&gt;

&lt;p&gt;The indexing layer is where vector databases earn their keep. Three families of algorithms dominate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HNSW (Hierarchical Navigable Small World)&lt;/strong&gt; builds a multi-layered graph where each node connects to its nearest neighbors. Searching is like navigating a skip list—you start at the top layer with long-range connections and drill down to finer layers. HNSW offers excellent query speed (sub-millisecond for millions of vectors) and high recall, but it requires the entire index to fit in memory. For 100 million 768-dimensional vectors stored as float32, that's roughly 300 GB of RAM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IVF (Inverted File Index)&lt;/strong&gt; partitions the vector space into clusters using k-means. At query time, it only searches the clusters closest to the query vector, skipping the rest. IVF uses less memory than HNSW and works well with disk-based storage, but recall degrades if you search too few clusters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PQ (Product Quantization)&lt;/strong&gt; compresses vectors by splitting them into sub-vectors and quantizing each sub-vector to its nearest centroid in a learned codebook. This dramatically reduces memory—a 768-dimensional float32 vector (3,072 bytes) can be compressed to 96 bytes. The trade-off is lower accuracy, especially for fine-grained similarity.&lt;/p&gt;

&lt;p&gt;In practice, production systems often combine these. IVF+PQ is common for billion-scale datasets where memory is a constraint. HNSW alone works well up to tens of millions of vectors if you have the RAM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Use Cases
&lt;/h2&gt;

&lt;p&gt;The reason vector databases have gotten so much attention is that &lt;strong&gt;semantic search&lt;/strong&gt; and related workloads are showing up everywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Semantic search&lt;/strong&gt; — Search by meaning instead of keywords. A query for "affordable flights to warm destinations" finds results about "cheap tickets to tropical locations." This is the most common use case and the one driving most adoption.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RAG (Retrieval-Augmented Generation)&lt;/strong&gt; — LLM applications retrieve relevant context from a vector database before generating a response. This is how most production chatbots and AI assistants ground their answers in real data instead of hallucinating.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Recommendation engines&lt;/strong&gt; — Embed users and items into the same vector space, then recommend items whose vectors are closest to a user's vector. Spotify and YouTube use variations of this approach at massive scale.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Image search&lt;/strong&gt; — Embed images and text into a shared space (using models like CLIP) so users can search photos with natural language or find visually similar images.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Anomaly detection&lt;/strong&gt; — In fraud detection or security monitoring, normal behavior forms clusters in vector space. Data points far from any cluster are flagged as anomalies.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When You DON'T Need a Vector Database
&lt;/h2&gt;

&lt;p&gt;Here's where the industry conversation often goes sideways. Not every application needs a dedicated vector database. Consider skipping one if:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your data is small.&lt;/strong&gt; If you have fewer than 100,000 vectors, brute-force search with a library like FAISS or even NumPy is fast enough. You can keep everything in memory on a single machine and get sub-50ms query times without any indexing at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need exact keyword matching.&lt;/strong&gt; Vector search is fuzzy by nature. If your users search for SKUs, error codes, or legal citations, you need traditional full-text search (Elasticsearch, PostgreSQL), not approximate nearest neighbors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You don't want to manage embeddings.&lt;/strong&gt; Running embedding models, building ingestion pipelines, choosing indexing parameters, tuning recall vs. latency—this is real operational overhead. If search is a feature of your product (not &lt;em&gt;the&lt;/em&gt; product), that overhead may not be worth it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pgvector is enough for your scale.&lt;/strong&gt; PostgreSQL's pgvector extension supports HNSW indexing and handles millions of vectors reasonably well. If you're already running Postgres and your dataset is under 5–10 million vectors, adding a vector column might be all you need. No new infrastructure required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Options Landscape
&lt;/h2&gt;

&lt;p&gt;When you &lt;em&gt;do&lt;/em&gt; need vector search, you have a spectrum of options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-hosted databases&lt;/strong&gt; like Milvus, Qdrant, and Weaviate give you full control. You manage deployment, scaling, backups, and tuning. This makes sense when you have strict data residency requirements, need to customize the indexing pipeline, or have a team comfortable with infrastructure operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Managed vector databases&lt;/strong&gt; like Pinecone, managed Weaviate, or Zilliz Cloud handle the infrastructure for you. You get an API, they manage the clusters. Pricing is typically based on storage and queries—expect $70–300/month for a moderately sized workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip the database entirely.&lt;/strong&gt; If what you actually need is semantic search or image search in your application, you don't necessarily need to manage vectors at all. Search APIs like &lt;a href="https://vecstore.app" rel="noopener noreferrer"&gt;Vecstore&lt;/a&gt; handle embedding generation, vector storage, and retrieval behind a single REST API—three endpoints, sub-200ms responses, 100+ languages. You send text or images, you get ranked results back. No models to run, no indexes to tune.&lt;/p&gt;

&lt;p&gt;This last option is worth considering honestly. A vector database is a means to an end. If the end is "my users need good search," the question isn't "which vector database should I use" but "what's the simplest way to ship this."&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Approach
&lt;/h2&gt;

&lt;p&gt;The decision comes down to how central vector search is to your product:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Recommended approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Search is a feature, not the product&lt;/td&gt;
&lt;td&gt;Managed search API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5M+ vectors, need full control&lt;/td&gt;
&lt;td&gt;Self-hosted vector DB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Already on Postgres, moderate scale&lt;/td&gt;
&lt;td&gt;pgvector extension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Building a RAG pipeline for an LLM app&lt;/td&gt;
&lt;td&gt;Managed vector DB or search API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Research or prototyping&lt;/td&gt;
&lt;td&gt;FAISS or in-memory brute force&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When to use a vector database is ultimately a question of scale, control, and how much infrastructure you're willing to own. For a lot of teams, the answer is less infrastructure than they think.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Vector databases are a genuinely useful technology solving a real problem: fast similarity search over high-dimensional data. They're not magic, and they're not always necessary. Understanding the mechanics—embeddings, ANN algorithms, indexing trade-offs—helps you make a clear-eyed decision about whether you need one, and if so, which kind.&lt;/p&gt;

&lt;p&gt;Start with the problem, not the technology. If your problem is "users need to search by meaning," you have options ranging from a Postgres extension to a fully managed search API. Pick the one that matches your team's capacity and your application's actual requirements.&lt;/p&gt;

</description>
      <category>semanticsearch</category>
    </item>
    <item>
      <title>Vecstore vs Pinecone: When You Don't Need a Raw Vector Database</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Sun, 12 Apr 2026 15:16:06 +0000</pubDate>
      <link>https://dev.to/kencho/vecstore-vs-pinecone-when-you-dont-need-a-raw-vector-database-2e2e</link>
      <guid>https://dev.to/kencho/vecstore-vs-pinecone-when-you-dont-need-a-raw-vector-database-2e2e</guid>
      <description>&lt;p&gt;Pinecone is usually the first name that comes up when someone starts looking into vector search. It raised $100M, it has a big brand, and for a while it was the only real managed option in the space. If you needed to store and query vectors, Pinecone was the answer.&lt;/p&gt;

&lt;p&gt;But here's the thing most teams figure out three months in: storing and querying vectors is not the same as having search that works.&lt;/p&gt;

&lt;p&gt;Pinecone is a vector index. You bring your own embeddings, push them in, and query by similarity. That's it. Everything else, the embedding pipeline, the data syncing, the actual search experience, is on you.&lt;/p&gt;

&lt;p&gt;Vecstore takes a different approach. You send in your data (text, images, whatever) and get working search back. No embedding step, no pipeline, no second database to keep in sync.&lt;/p&gt;

&lt;p&gt;These are fundamentally different products solving different problems. And picking the wrong one costs you months.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Pinecone Actually Is
&lt;/h2&gt;

&lt;p&gt;Pinecone is a managed vector index. That sounds simple, but it's worth understanding what it means in practice.&lt;/p&gt;

&lt;p&gt;When you use Pinecone, your workflow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You store your actual data somewhere else (Postgres, DynamoDB, wherever)&lt;/li&gt;
&lt;li&gt;You generate embeddings for that data using a separate service (OpenAI, Cohere, a self-hosted model)&lt;/li&gt;
&lt;li&gt;You push those embeddings into Pinecone along with some metadata (capped at 40KB per vector)&lt;/li&gt;
&lt;li&gt;At query time, you generate an embedding for the search query using the same model&lt;/li&gt;
&lt;li&gt;Pinecone returns the closest vector IDs&lt;/li&gt;
&lt;li&gt;You take those IDs back to your primary database to fetch the actual records&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's a minimum of three services for one search query: your database, your embedding API, and Pinecone. And you're responsible for keeping all three in sync.&lt;/p&gt;

&lt;p&gt;If your source data changes, you need to re-embed and re-upsert into Pinecone yourself. There's no built-in sync mechanism. If your embedding model gets updated, you need to re-embed everything. If Pinecone's metadata limit doesn't cover your use case, you're making extra round-trips to your primary database on every single query.&lt;/p&gt;

&lt;p&gt;This is fine if you're building something custom where you need full control over every layer. Some teams genuinely need that. But most teams building product search, content discovery, or recommendation features don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Vecstore Actually Is
&lt;/h2&gt;

&lt;p&gt;Vecstore is a search API. You send in your data and search it. That's the whole workflow.&lt;/p&gt;

&lt;p&gt;There's no embedding step you manage. No second database. No pipeline to build. You create a database, insert your records (text, images, or both), and call the search endpoint. Vecstore handles embedding generation, indexing, and retrieval internally.&lt;/p&gt;

&lt;p&gt;A search query hits one endpoint and returns results. Not vector IDs that you then resolve against another database. Actual results.&lt;/p&gt;

&lt;p&gt;This also means you're not locked into a specific embedding model or responsible for migrating when better models come out. That's handled on Vecstore's side.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Cost of "Just a Vector Index"
&lt;/h2&gt;

&lt;p&gt;The pitch for Pinecone sounds lean: "We'll store your vectors and make them searchable." But in practice, the surrounding infrastructure adds up fast.&lt;/p&gt;

&lt;p&gt;Here's what a typical Pinecone-based search stack actually requires:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embedding generation.&lt;/strong&gt; You need an API or a self-hosted model to convert your data into vectors. OpenAI's embedding API charges per token. Running your own model means GPU instances. Either way, it's an additional cost and an additional point of failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A primary database.&lt;/strong&gt; Pinecone doesn't replace your database. It sits alongside it. You're still paying for and maintaining your primary data store. And you're building the glue code that keeps them in sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sync infrastructure.&lt;/strong&gt; When a record is created, updated, or deleted in your primary database, you need a process that re-embeds and upserts into Pinecone. This is usually a queue, a worker, and a monitoring setup. It's not complicated, but it's one more thing that can break at 2 AM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query resolution.&lt;/strong&gt; Pinecone returns vector IDs with limited metadata. For most use cases, that means a second database query on every search to fetch the actual content. This adds latency and complexity.&lt;/p&gt;

&lt;p&gt;Teams that start with Pinecone often estimate integration at a few days. The embedding pipeline, sync layer, and query resolution layer tend to push that into weeks. And ongoing maintenance is a permanent tax on engineering time.&lt;/p&gt;

&lt;p&gt;With Vecstore, you skip all of this. One API, one data store, one bill. Insert your data, call the search endpoint. The infrastructure question is answered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Pinecone Makes Sense
&lt;/h2&gt;

&lt;p&gt;Pinecone isn't the wrong choice for every project. There are legitimate use cases where a raw vector index is what you want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom ML pipelines.&lt;/strong&gt; If you're a team with ML engineers who need control over embedding models, fine-tuning, and vector operations, a raw index makes sense. You probably have opinions about which model to use and want to swap them freely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RAG for LLMs.&lt;/strong&gt; If you're building a retrieval-augmented generation pipeline where you need tight control over chunking strategies, embedding dimensions, and retrieval scoring, Pinecone gives you that control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Research and experimentation.&lt;/strong&gt; If you're testing different embedding models or building a novel retrieval system, the flexibility of a raw vector index is useful. You're not looking for a finished product. You're building one.&lt;/p&gt;

&lt;p&gt;The common thread is that these teams already have ML infrastructure and expertise. Pinecone is a component they plug into a larger system they own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Vecstore Makes Sense
&lt;/h2&gt;

&lt;p&gt;Vecstore is built for teams that need search to work, not teams that want to build search infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product search.&lt;/strong&gt; Your e-commerce app needs users to find products by describing what they want. "Warm jacket for hiking" should return insulated outdoor jackets even if no product is titled that way. You need this to work out of the box, not after building an embedding pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image search.&lt;/strong&gt; You need reverse image search, text-to-image search, OCR search, or face search. Pinecone has no image understanding. You'd need to bring your own CLIP model, run inference, and push those vectors in. With Vecstore, you upload the image and search it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multilingual search.&lt;/strong&gt; Your users search in Japanese, Korean, German, Spanish. Vecstore handles 100+ languages natively from a single index. With Pinecone, multilingual quality depends entirely on whichever embedding model you chose, and you're responsible for evaluating that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content moderation.&lt;/strong&gt; Your platform accepts user-uploaded images and you need NSFW detection. Vecstore includes this across 52 categories. With Pinecone, content moderation is a completely separate problem you solve with a completely separate service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small to mid-size teams.&lt;/strong&gt; If you don't have ML engineers on staff, building and maintaining an embedding pipeline is a distraction from your actual product. Vecstore removes that entire category of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side-by-Side Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Vecstore&lt;/th&gt;
&lt;th&gt;Pinecone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it is&lt;/td&gt;
&lt;td&gt;Search API&lt;/td&gt;
&lt;td&gt;Vector index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedding generation&lt;/td&gt;
&lt;td&gt;Handled for you&lt;/td&gt;
&lt;td&gt;Bring your own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data storage&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Requires separate database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image search&lt;/td&gt;
&lt;td&gt;Native (reverse, text-to-image, face, OCR)&lt;/td&gt;
&lt;td&gt;Not available (BYO model)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text search&lt;/td&gt;
&lt;td&gt;Semantic + hybrid&lt;/td&gt;
&lt;td&gt;Vector similarity only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multilingual&lt;/td&gt;
&lt;td&gt;100+ languages, one index&lt;/td&gt;
&lt;td&gt;Depends on your embedding model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NSFW detection&lt;/td&gt;
&lt;td&gt;52 categories built-in&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data sync&lt;/td&gt;
&lt;td&gt;Not needed (single source)&lt;/td&gt;
&lt;td&gt;Your responsibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata limit&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;40KB per vector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query result&lt;/td&gt;
&lt;td&gt;Full records&lt;/td&gt;
&lt;td&gt;Vector IDs + limited metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum cost&lt;/td&gt;
&lt;td&gt;Free tier available&lt;/td&gt;
&lt;td&gt;$50/month (Standard)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup time&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;td&gt;Days to weeks (with pipeline)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Pricing Reality
&lt;/h2&gt;

&lt;p&gt;Pinecone's free tier is limited to 2GB of storage and restricted to US regions. After that, the Standard plan starts at $50/month with read units at $16 per million and storage at $0.33/GB. But that's just the Pinecone bill. You also pay for your embedding API, your primary database, and whatever compute runs your sync pipeline.&lt;/p&gt;

&lt;p&gt;Vecstore starts with free credits on signup. After that, it's $1.60 per 1K operations. One bill, one service. No separate embedding costs, no second database to provision.&lt;/p&gt;

&lt;p&gt;For a team running a million searches a month, the total cost difference between a Pinecone-based stack (Pinecone + embedding API + primary database + sync infrastructure) and Vecstore tends to be significant. Not because Pinecone itself is expensive, but because everything around it adds up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vendor Lock-In Question
&lt;/h2&gt;

&lt;p&gt;This is worth addressing directly because it comes up a lot.&lt;/p&gt;

&lt;p&gt;Pinecone is closed-source. There is no self-hosted production option. "Pinecone Local" exists for testing, but it's an in-memory emulator, not a production deployment. You're fully dependent on Pinecone's infrastructure, pricing decisions, and roadmap.&lt;/p&gt;

&lt;p&gt;Vecstore is also a managed service. You're trusting a vendor either way. The difference is that with Vecstore, your data and your search live in one place. With Pinecone, your data lives in your database and your vectors live in Pinecone. If you ever want to move off Pinecone, you need to re-architect your entire search pipeline. With Vecstore, you swap one API for another.&lt;/p&gt;

&lt;p&gt;Neither option gives you the portability of self-hosted open source. If that's your priority, look at Qdrant or Milvus. But if you're choosing between managed services, the migration path matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Pinecone is a solid vector index for teams that are building custom ML systems and want a managed place to store and query their vectors. It's a component, not a finished product.&lt;/p&gt;

&lt;p&gt;Vecstore is a finished search product for teams that need search to work today. Text search, image search, multilingual, content moderation, all from one API. No embedding pipelines, no sync layers, no second database.&lt;/p&gt;

&lt;p&gt;The question to ask isn't "which vector database should I use?" It's "do I need a vector database at all, or do I just need search that works?"&lt;/p&gt;

&lt;p&gt;For most teams, it's the second one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;Try Vecstore free&lt;/a&gt; or &lt;a href="https://vecstore.app/docs" rel="noopener noreferrer"&gt;explore the API docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>comparisons</category>
      <category>semanticsearch</category>
    </item>
    <item>
      <title>Vecstore vs Imagga: We Tested Both Image Search APIs</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Sun, 12 Apr 2026 15:15:30 +0000</pubDate>
      <link>https://dev.to/kencho/vecstore-vs-imagga-we-tested-both-image-search-apis-5316</link>
      <guid>https://dev.to/kencho/vecstore-vs-imagga-we-tested-both-image-search-apis-5316</guid>
      <description>&lt;p&gt;I wanted to understand how Imagga's visual search compares to ours, so I signed up, got API keys, and tested both APIs against the same images. The two products turned out to be more different than I expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Imagga's "visual search" is built on image categorization and tag matching. Vecstore uses vector embeddings for actual visual similarity. Vecstore is about 8x faster on search (300ms vs 2.5s), doesn't require a separate database, supports text-to-image search, and auto-indexes without manual training. Imagga is stronger at structured image tagging, color extraction, and background removal.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Two Approaches Differ
&lt;/h2&gt;

&lt;p&gt;The biggest takeaway from testing wasn't speed. It was that the two APIs use fundamentally different approaches to image search.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imagga&lt;/strong&gt; categorizes images into tags using WordNet taxonomy and then finds other images that share similar tags. When you search for a dog photo, it first categorizes it as &lt;code&gt;border_collie.n.01&lt;/code&gt; with 93.4% confidence, then finds other images that were categorized similarly. It's a categorization-first approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vecstore&lt;/strong&gt; converts images into vector embeddings that represent visual meaning, then finds the closest matches in vector space. It doesn't categorize or tag anything. It compares what images actually look like.&lt;/p&gt;

&lt;p&gt;Both are valid approaches. They solve different problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Imagga's Own Demo Needs a Second Database
&lt;/h2&gt;

&lt;p&gt;This was the most interesting finding. I opened the network tab on Imagga's visual search demo and watched what happens when you search.&lt;/p&gt;

&lt;p&gt;Two requests fire:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request 1&lt;/strong&gt; goes to Imagga's API and returns categories + image IDs:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-gray"&gt;// Imagga search response&lt;/span&gt;
{
  &lt;span class="hl-green"&gt;"categories"&lt;/span&gt;: [{
    &lt;span class="hl-green"&gt;"name"&lt;/span&gt;: &lt;span class="hl-green"&gt;"border_collie.n.01"&lt;/span&gt;,
    &lt;span class="hl-green"&gt;"confidence"&lt;/span&gt;: &lt;span class="hl-blue"&gt;93.41&lt;/span&gt;
  }],
  &lt;span class="hl-green"&gt;"images"&lt;/span&gt;: [{
    &lt;span class="hl-green"&gt;"id"&lt;/span&gt;: &lt;span class="hl-green"&gt;"img_1770651039261-q6ozvx531"&lt;/span&gt;,
    &lt;span class="hl-green"&gt;"distance"&lt;/span&gt;: &lt;span class="hl-blue"&gt;0.387&lt;/span&gt;
  }]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;No image URLs. No metadata. Just IDs and distances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request 2&lt;/strong&gt; goes to a Supabase database to resolve those IDs:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-gray"&gt;// Second request to Supabase&lt;/span&gt;
GET /rest/v1/visual_search_images
  ?select=save_id,image_url,file_name
  &amp;amp;save_id=in.(img_1770651039261-q6ozvx531,img_1770651039253-ggmgwfihy,...)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Their own demo needs a separate Postgres database just to display search results. That's not a limitation of the demo, it's how the API works. Imagga returns IDs, you resolve them yourself.&lt;/p&gt;

&lt;p&gt;On Vecstore, one request returns everything:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-gray"&gt;// Vecstore search response&lt;/span&gt;
{
  &lt;span class="hl-green"&gt;"vector_id"&lt;/span&gt;: &lt;span class="hl-green"&gt;"abc123"&lt;/span&gt;,
  &lt;span class="hl-green"&gt;"score"&lt;/span&gt;: &lt;span class="hl-blue"&gt;0.94&lt;/span&gt;,
  &lt;span class="hl-green"&gt;"metadata"&lt;/span&gt;: {
    &lt;span class="hl-green"&gt;"image_url"&lt;/span&gt;: &lt;span class="hl-green"&gt;"https://..."&lt;/span&gt;,
    &lt;span class="hl-green"&gt;"name"&lt;/span&gt;: &lt;span class="hl-green"&gt;"Border Collie"&lt;/span&gt;,
    &lt;span class="hl-green"&gt;"category"&lt;/span&gt;: &lt;span class="hl-green"&gt;"pets"&lt;/span&gt;,
    &lt;span class="hl-green"&gt;"price"&lt;/span&gt;: &lt;span class="hl-blue"&gt;45.00&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Image URLs, custom metadata, similarity scores. No second database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Comparison
&lt;/h2&gt;

&lt;p&gt;Here's what each setup looks like in practice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imagga:&lt;/strong&gt;&lt;br&gt;
Your app → Imagga API (returns IDs) → Your database (resolve URLs) → Your storage (serve images)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vecstore:&lt;/strong&gt;&lt;br&gt;
Your app → Vecstore API (returns full results with URLs) → Your storage (serve images)&lt;/p&gt;

&lt;p&gt;With Imagga you're maintaining a separate database that maps image IDs to URLs and metadata. With Vecstore that mapping lives inside the search itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed Comparison
&lt;/h2&gt;

&lt;p&gt;I benchmarked every operation with real API calls.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/images/blog/vecstore-vs-imagga/speed-chart.webp" class="article-body-image-wrapper"&gt;&lt;img src="/assets/images/blog/vecstore-vs-imagga/speed-chart.webp" alt="Speed comparison chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Imagga&lt;/th&gt;
&lt;th&gt;Vecstore&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Insert image&lt;/td&gt;
&lt;td&gt;3.8 - 4.7s&lt;/td&gt;
&lt;td&gt;~200ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Train index&lt;/td&gt;
&lt;td&gt;~0.9s (manual step)&lt;/td&gt;
&lt;td&gt;Not needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search query&lt;/td&gt;
&lt;td&gt;2.0 - 2.5s&lt;/td&gt;
&lt;td&gt;~300ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Insert to searchable&lt;/td&gt;
&lt;td&gt;~5-6s + manual retrain&lt;/td&gt;
&lt;td&gt;~200ms (instant)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Vecstore's 300ms breaks down to about 90ms for embedding generation and 5-8ms for the actual vector search. The rest is network overhead.&lt;/p&gt;

&lt;p&gt;Imagga's 2-2.5 seconds covers the image categorization and tag matching. That doesn't include the second call to your own database to resolve image URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The WordNet Labels
&lt;/h2&gt;

&lt;p&gt;Imagga uses WordNet taxonomy for its category names. In practice that looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;border_collie.n.01&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loggerhead.n.02&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;turbine.n.01&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Persian_cat.n.01&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;.n.01&lt;/code&gt; suffix means "noun, first sense." This is standard in computational linguistics but not something most developers want to parse or display to users. You'd need a mapping layer to convert these to human-readable labels.&lt;/p&gt;

&lt;p&gt;Vecstore doesn't return category labels at all. It returns similarity scores and whatever metadata you stored when inserting the image. You control the naming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Index Training
&lt;/h2&gt;

&lt;p&gt;With Imagga, after you insert images into an index, you need to manually call a train endpoint before those images become searchable. When you add new images later, you retrain again.&lt;/p&gt;

&lt;p&gt;Here's what the insert-train-search flow looks like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-gray"&gt;# Step 1: Insert images&lt;/span&gt;
&lt;span class="hl-blue"&gt;curl&lt;/span&gt; -X POST "https://api.imagga.com/v2/images-similarity/index/save-image" \
  -u "api_key:api_secret" \
  -F "image_url=https://example.com/dog.jpg" \
  -F "index_name=my_index" \
  -F "save_id=img_001"

&lt;span class="hl-gray"&gt;# Step 2: Manually train the index (required before search works)&lt;/span&gt;
&lt;span class="hl-blue"&gt;curl&lt;/span&gt; -X POST "https://api.imagga.com/v2/images-similarity/index/train" \
  -u "api_key:api_secret" \
  -F "index_name=my_index"

&lt;span class="hl-gray"&gt;# Step 3: Now you can search&lt;/span&gt;
&lt;span class="hl-blue"&gt;curl&lt;/span&gt; -X GET "https://api.imagga.com/v2/images-similarity/index/search" \
  -u "api_key:api_secret" \
  -d "image_url=https://example.com/query.jpg" \
  -d "index_name=my_index"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For apps where users upload images regularly (marketplaces, social platforms, photo libraries), this means either retraining after every upload or batching retrains and accepting that new images won't be searchable immediately.&lt;/p&gt;

&lt;p&gt;On Vecstore, insert and search:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-gray"&gt;# Insert (instantly searchable)&lt;/span&gt;
&lt;span class="hl-blue"&gt;curl&lt;/span&gt; -X POST "https://api.vecstore.app/api/databases/{id}/documents" \
  -H "X-API-Key: your_key" \
  -H "Content-Type: application/json" \
  -d '{"image_url": "https://example.com/dog.jpg"}'

&lt;span class="hl-gray"&gt;# Search&lt;/span&gt;
&lt;span class="hl-blue"&gt;curl&lt;/span&gt; -X POST "https://api.vecstore.app/api/databases/{id}/search" \
  -H "X-API-Key: your_key" \
  -H "Content-Type: application/json" \
  -d '{"image_url": "https://example.com/query.jpg", "top_k": 10}'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;No training step. Images are searchable the moment you insert them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Text-to-Image Search
&lt;/h2&gt;

&lt;p&gt;Imagga's visual search is image-to-image only. You search by uploading a photo.&lt;/p&gt;

&lt;p&gt;Vecstore supports both. You can type "wind turbine on a hillside" and get matching images back. This works because the model understands both text and images in the same embedding space.&lt;/p&gt;

&lt;p&gt;If your users need to search by description (e-commerce, stock photography, content discovery), this matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Metadata Filtering on Imagga
&lt;/h2&gt;

&lt;p&gt;Since Imagga returns only IDs, there's no way to filter results at search time. You can't say "find similar images where price is under $50" or "find similar images in the shoes category."&lt;/p&gt;

&lt;p&gt;With Vecstore, metadata is stored alongside the vectors and returned with results. Filtering by metadata fields is part of the search query itself.&lt;/p&gt;

&lt;p&gt;For e-commerce and marketplace use cases where you need to combine visual similarity with business logic, this is a meaningful difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;The pricing models are different. Imagga charges monthly subscriptions where unused requests expire at the end of each billing cycle. Visual search requires the $79/mo Indie plan or higher.&lt;/p&gt;

&lt;p&gt;Vecstore sells credit packs that never expire. All features are included on every plan.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/images/blog/vecstore-vs-imagga/cost-chart.webp" class="article-body-image-wrapper"&gt;&lt;img src="/assets/images/blog/vecstore-vs-imagga/cost-chart.webp" alt="Pricing comparison chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Volume&lt;/th&gt;
&lt;th&gt;Imagga&lt;/th&gt;
&lt;th&gt;Vecstore&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5K operations&lt;/td&gt;
&lt;td&gt;$79/mo&lt;/td&gt;
&lt;td&gt;$8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;70K operations&lt;/td&gt;
&lt;td&gt;$79/mo&lt;/td&gt;
&lt;td&gt;~$66&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;300K operations&lt;/td&gt;
&lt;td&gt;$349/mo&lt;/td&gt;
&lt;td&gt;~$240&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At lower volumes, Vecstore is significantly cheaper. At higher volumes the gap narrows. The main structural difference is that Imagga locks features behind higher tiers (face recognition needs the $349/mo Pro plan) while Vecstore includes everything on all plans.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Imagga Does Well
&lt;/h2&gt;

&lt;p&gt;Imagga's core strength is image tagging and categorization. If you need to auto-generate structured labels for images, extract dominant colors, remove backgrounds, or read barcodes, Imagga has mature, well-documented tools for that. Their categorization engine is solid and they've been doing this for a long time.&lt;/p&gt;

&lt;p&gt;Their visual search is built on top of that categorization engine, which makes sense for their product. It's a different approach than embedding-based search, with its own strengths: structured categories, confidence scores for each label, and a well-defined taxonomy.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Which
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Imagga is a better fit when you need:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structured image tagging and categorization&lt;/li&gt;
&lt;li&gt;Color extraction and analysis&lt;/li&gt;
&lt;li&gt;Background removal&lt;/li&gt;
&lt;li&gt;Barcode and text recognition in images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Vecstore is a better fit when you need:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visual similarity search (find images that look alike)&lt;/li&gt;
&lt;li&gt;Text-to-image search (describe what you want, get matching images)&lt;/li&gt;
&lt;li&gt;Face search across an image library&lt;/li&gt;
&lt;li&gt;Content moderation (NSFW detection)&lt;/li&gt;
&lt;li&gt;Instant indexing without manual training steps&lt;/li&gt;
&lt;li&gt;Search results with image URLs and metadata in one call&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your use case is primarily search, the differences in speed, developer experience, and architecture add up. Vecstore was built for search from the ground up. Imagga was built for image understanding, and search is one application of that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vecstore.app/compare/vecstore-vs-imagga" rel="noopener noreferrer"&gt;See the full side-by-side comparison&lt;/a&gt; or &lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;try Vecstore free&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>comparisons</category>
      <category>imagesearch</category>
    </item>
    <item>
      <title>Vecstore vs Algolia: Semantic Search vs Keyword Search</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Sun, 12 Apr 2026 15:14:54 +0000</pubDate>
      <link>https://dev.to/kencho/vecstore-vs-algolia-semantic-search-vs-keyword-search-1hab</link>
      <guid>https://dev.to/kencho/vecstore-vs-algolia-semantic-search-vs-keyword-search-1hab</guid>
      <description>&lt;p&gt;Algolia is the default answer when someone says "I need search." And for good reason — it's fast, reliable, and has been the industry standard for years. If you need autocomplete, faceted filtering, or typo tolerance on product names, Algolia is hard to beat.&lt;/p&gt;

&lt;p&gt;But search has changed. Users don't just type exact product names anymore. They describe what they want in plain language. They upload photos to find similar products. They search in their own language and expect it to just work.&lt;/p&gt;

&lt;p&gt;That's a fundamentally different problem, and it requires a fundamentally different approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Algolia Searches
&lt;/h2&gt;

&lt;p&gt;Algolia is a keyword search engine. It matches the words in a query against the words in your records. If a user types "running shoes," Algolia finds records containing "running" and "shoes."&lt;/p&gt;

&lt;p&gt;It's enhanced with powerful features on top — typo tolerance, faceting, synonyms, and instant search-as-you-type. These make the keyword matching experience feel polished. But the underlying mechanism is still matching strings.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"car that is quick"&lt;/strong&gt; won't find a record that says "car that is fast" — the word "quick" doesn't appear in the data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synonyms require manual configuration&lt;/strong&gt; — you have to tell Algolia that "quick" and "fast" mean the same thing, for every synonym pair, in every language&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multilingual search requires per-index setup&lt;/strong&gt; — you can't just throw a Japanese query at an English index and expect results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For structured, predictable search queries, this works great. For everything else, it creates friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Vecstore Searches
&lt;/h2&gt;

&lt;p&gt;Vecstore is a semantic search engine. Instead of matching words, it matches meaning.&lt;/p&gt;

&lt;p&gt;When you insert a document or image, Vecstore converts it into a vector — a mathematical representation of its meaning. At search time, the query is also converted into a vector, and Vecstore finds the closest matches by meaning, not by words.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"car that is quick"&lt;/strong&gt; finds "car that is fast" because the two phrases mean the same thing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No synonym configuration&lt;/strong&gt; — the model understands language natively&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100+ languages work out of the box&lt;/strong&gt; — semantic search works natively in Japanese, Korean, Arabic, and 100+ more languages without any extra configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images are searchable by content&lt;/strong&gt; — not by tags someone manually attached&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where Each One Wins
&lt;/h2&gt;

&lt;p&gt;This isn't about one being better than the other. They solve different problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Algolia is the better choice when you need:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Autocomplete.&lt;/strong&gt; Search-as-you-type with instant dropdown suggestions. Algolia's prefix matching is purpose-built for this. If your users expect results before they finish typing, Algolia delivers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Faceted navigation.&lt;/strong&gt; Filtering a product catalog by brand, size, color, price range — this is Algolia's bread and butter. It's deeply integrated and fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typo tolerance on exact terms.&lt;/strong&gt; If users are searching for specific product names, part numbers, or SKUs, Algolia's fuzzy matching on exact strings is excellent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vecstore is the better choice when you need:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Natural language queries.&lt;/strong&gt; When users type "something to keep my coffee hot" instead of "insulated travel mug," semantic search finds the right results. Keyword search doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image search.&lt;/strong&gt; Vecstore supports reverse image search (upload a photo, find similar ones), text-to-image search (describe what you want, get matching images), face search, and OCR search (find images by the text inside them). Algolia has no native equivalent — their image approach requires external tagging services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multilingual search.&lt;/strong&gt; Vecstore understands 100+ languages natively. Your Japanese users search in Japanese and get accurate semantic results — no per-language index, no translation layers, no extra configuration. Same API, same accuracy, every language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content moderation.&lt;/strong&gt; Vecstore includes built-in NSFW detection across 52 categories. If your platform accepts user-uploaded images, this is one API call instead of a separate moderation pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Image Search Gap
&lt;/h2&gt;

&lt;p&gt;This is where the difference is most visible. Algolia doesn't do image search natively. Their documentation recommends using external services like Google Vision or AWS Rekognition to generate tags, then storing those tags as searchable attributes in your Algolia index.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You need a separate tagging service&lt;/li&gt;
&lt;li&gt;You pay for both the tagging API and Algolia&lt;/li&gt;
&lt;li&gt;Search quality is limited to whatever tags were generated&lt;/li&gt;
&lt;li&gt;A search for "vintage rucksack" won't match an image tagged as ["backpack", "leather", "brown"]&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With Vecstore, you insert the image and search it. A search for "vintage rucksack" finds a leather backpack because the model understands visual similarity — not because someone tagged it correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side-by-Side Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Vecstore&lt;/th&gt;
&lt;th&gt;Algolia&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Search type&lt;/td&gt;
&lt;td&gt;Semantic (meaning)&lt;/td&gt;
&lt;td&gt;Keyword (strings)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image search&lt;/td&gt;
&lt;td&gt;Native — reverse, text-to-image, face, OCR&lt;/td&gt;
&lt;td&gt;Requires external tagging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Synonym handling&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;td&gt;Manual configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multilingual&lt;/td&gt;
&lt;td&gt;100+ languages, one index&lt;/td&gt;
&lt;td&gt;Per-index setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Natural language queries&lt;/td&gt;
&lt;td&gt;Works natively&lt;/td&gt;
&lt;td&gt;Needs rules and synonyms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NSFW detection&lt;/td&gt;
&lt;td&gt;52 categories built-in&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Autocomplete&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Faceted filtering&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;From $1.60/1K operations&lt;/td&gt;
&lt;td&gt;Per search + per record&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We're being honest about the gaps. Vecstore doesn't do autocomplete or faceted filtering. If those are critical to your product, Algolia is the right choice — or use both.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Both
&lt;/h2&gt;

&lt;p&gt;For many products, the answer is both. Use Algolia for your search bar autocomplete and faceted catalog navigation. Use Vecstore for natural language search, image search, and content moderation.&lt;/p&gt;

&lt;p&gt;The two aren't mutually exclusive. They're different tools for different problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Algolia is an excellent product that earned its place as the industry standard for keyword search. If your users search by typing exact product names into a search bar, it's the right tool.&lt;/p&gt;

&lt;p&gt;But if your users describe what they're looking for, search by uploading images, or need search that works in their language — keyword matching isn't enough. That's what Vecstore is built for.&lt;/p&gt;

&lt;p&gt;The right question isn't "which is better?" It's "what kind of search do my users actually need?"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;Try Vecstore free&lt;/a&gt; or &lt;a href="https://vecstore.app/compare/vecstore-vs-algolia" rel="noopener noreferrer"&gt;see the full comparison&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>comparisons</category>
      <category>semanticsearch</category>
    </item>
    <item>
      <title>How to Search Images by Text Description (Text-to-Image Search)</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Sun, 12 Apr 2026 15:14:18 +0000</pubDate>
      <link>https://dev.to/kencho/how-to-search-images-by-text-description-text-to-image-search-2b6j</link>
      <guid>https://dev.to/kencho/how-to-search-images-by-text-description-text-to-image-search-2b6j</guid>
      <description>&lt;p&gt;Your user types "vintage wooden chair with armrests" into a search bar. Your app returns photos of vintage wooden chairs with armrests from a database of 100,000 product images. None of those images were tagged with those words. Nobody manually labeled them. The search just understands what the user is describing.&lt;/p&gt;

&lt;p&gt;That's text-to-image search. And it's more practical than most people realize.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Text-to-Image Search?
&lt;/h2&gt;

&lt;p&gt;Text-to-image search lets users find images by describing what they want in plain language. Instead of browsing categories or filtering by tags, the user just says what they're looking for and gets matching images back.&lt;/p&gt;

&lt;p&gt;This is different from traditional image search where you need metadata. In a typical setup, someone has to tag each image with keywords like "chair," "wood," "vintage," "armrest." If they miss a tag, the image is invisible to search. If the user uses a word that doesn't match any tag (like "antique" instead of "vintage"), the search returns nothing.&lt;/p&gt;

&lt;p&gt;Text-to-image search skips all of that. The system understands both the text query and the visual content of the images, and matches them by meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The technology behind this is called a multimodal embedding model. The most well-known one is CLIP, built by OpenAI. Others include OpenCLIP, SigLIP, and SigLIP2.&lt;/p&gt;

&lt;p&gt;These models do something clever: they map both text and images into the same mathematical space. A photo of a red sports car and the text "red sports car" end up near each other in this space, even though one is pixels and the other is words.&lt;/p&gt;

&lt;p&gt;Here's the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;At index time&lt;/strong&gt;, each image in your database gets converted into a vector (an array of numbers, usually 768-1024 of them). This vector captures what the image looks like, what objects are in it, the scene, the colors, the composition.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;At search time&lt;/strong&gt;, the user's text query also gets converted into a vector in the same space.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The system finds&lt;/strong&gt; the image vectors that are closest to the query vector. "Closest" means most similar in meaning.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result: "cozy living room with fireplace" finds photos of cozy living rooms with fireplaces, even if nobody ever described those images that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It's Better Than Tags
&lt;/h2&gt;

&lt;p&gt;Manual tagging has been the standard for decades. It works, but it has real problems at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags are incomplete.&lt;/strong&gt; A person tagging a photo of a beach sunset might write "beach, sunset, ocean." They probably won't add "vacation, travel, golden hour, silhouette, waves, coastline, relaxation." But a user might search for any of those terms. With text-to-image search, all of those queries would find that image because the model understands the full visual content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags are inconsistent.&lt;/strong&gt; Different people tag differently. One person writes "sneakers," another writes "trainers," another writes "athletic shoes." With manual tagging, you either enforce a strict vocabulary (which limits what users can search for) or accept inconsistency (which creates gaps in search results).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags don't scale.&lt;/strong&gt; Tagging 100 images is tedious. Tagging 100,000 is a full-time job. Tagging 10 million requires a team. Text-to-image search has no per-image cost. You insert the images and they're searchable immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags can't capture everything.&lt;/strong&gt; How do you tag "mood" or "style"? A user searching for "minimalist interior" has a specific aesthetic in mind. You can't tag for that. But a model trained on millions of images understands what "minimalist" looks like visually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Text-to-Image Search Gets Used
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;E-commerce product search.&lt;/strong&gt; A customer types "casual blue dress for summer" and gets matching products. This works especially well when your catalog has thousands of items and your customers don't know the exact product names or categories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stock photography.&lt;/strong&gt; Designers search for "person working at laptop in coffee shop" and find relevant photos. This is how platforms like Shutterstock and Getty Images work. Without text-to-image search, finding the right stock photo means browsing hundreds of loosely tagged results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real estate.&lt;/strong&gt; "Modern kitchen with island and natural light" returns listing photos that match. Buyers know what they want to see. Text-to-image search lets them describe it instead of clicking through filters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fashion and styling.&lt;/strong&gt; "Oversized wool coat" or "streetwear outfit with jordans" returns visual matches from your product catalog or user-generated content. Fashion is inherently visual and hard to search with keywords alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content management.&lt;/strong&gt; Your company has 50,000 photos from events, product shoots, and marketing campaigns sitting in a DAM. Nobody can find anything because the tagging is inconsistent. Text-to-image search makes the entire library searchable without re-tagging a single image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Food and recipe apps.&lt;/strong&gt; "Pasta dish with fresh basil" finds matching food photos. This works because the model understands what basil looks like on a plate, not because someone tagged the image with "basil."&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Add It to Your App
&lt;/h2&gt;

&lt;p&gt;If you're building this yourself, you need a CLIP model running on a GPU server, a vector database to store image embeddings, and a pipeline to keep everything in sync. That's a real project (we wrote a &lt;a href="https://dev.to/blog/what-it-costs-to-search-1m-images"&gt;full cost breakdown&lt;/a&gt; if you're curious).&lt;/p&gt;

&lt;p&gt;With an API like Vecstore, the process is simpler:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Insert your images.&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-gray"&gt;POST&lt;/span&gt; &lt;span class="hl-purple"&gt;https://api.vecstore.app/api/databases/{id}/documents&lt;/span&gt;

&lt;span class="hl-blue"&gt;X-API-Key&lt;/span&gt;: &lt;span class="hl-green"&gt;your-api-key&lt;/span&gt;

Body:
  &lt;span class="hl-blue"&gt;image_url&lt;/span&gt;: &lt;span class="hl-green"&gt;"https://example.com/products/chair.jpg"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each image gets embedded automatically when you insert it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Search by text.&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-gray"&gt;POST&lt;/span&gt; &lt;span class="hl-purple"&gt;https://api.vecstore.app/api/databases/{id}/search&lt;/span&gt;

&lt;span class="hl-blue"&gt;X-API-Key&lt;/span&gt;: &lt;span class="hl-green"&gt;your-api-key&lt;/span&gt;

Body:
  &lt;span class="hl-blue"&gt;query&lt;/span&gt;: &lt;span class="hl-green"&gt;"vintage wooden chair with armrests"&lt;/span&gt;
  &lt;span class="hl-blue"&gt;top_k&lt;/span&gt;: &lt;span class="hl-green"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The API returns the most visually relevant images ranked by similarity score.&lt;/p&gt;

&lt;p&gt;That's it. No tagging, no CLIP model to run, no vector database to manage. The same database also supports &lt;a href="https://dev.to/blog/how-to-build-reverse-image-search"&gt;reverse image search&lt;/a&gt;, &lt;a href="https://dev.to/blog/how-to-add-face-search-to-your-app"&gt;face search&lt;/a&gt;, and &lt;a href="https://dev.to/blog/ocr-search-find-images-by-text"&gt;OCR search&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want a full tutorial with frontend code, we have step-by-step guides for &lt;a href="https://dev.to/blog/how-to-add-image-search-to-react"&gt;React&lt;/a&gt; and &lt;a href="https://dev.to/blog/how-to-add-semantic-search-to-nextjs"&gt;Next.js&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Text-to-Image Search vs. Image-to-Image Search
&lt;/h2&gt;

&lt;p&gt;These are related but different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text-to-image&lt;/strong&gt;: user types a description, gets matching images. Good when the user knows what they want but doesn't have a reference image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image-to-image&lt;/strong&gt; (reverse image search): user uploads a photo, gets visually similar images. Good when the user has a reference ("find me more like this") but can't describe it in words.&lt;/p&gt;

&lt;p&gt;Both use the same underlying technology (multimodal embeddings) and work against the same image database. Most production search features support both, letting users switch between typing a description and uploading a photo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;If you want to try text-to-image search on your own images, the fastest path is Vecstore's free tier. Insert some images, run a few text queries, and see how the results look.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;Try it free&lt;/a&gt; or &lt;a href="https://vecstore.app" rel="noopener noreferrer"&gt;check out the live demo&lt;/a&gt; on our homepage, which searches 10,000 paintings by description.&lt;/p&gt;

</description>
      <category>imagesearch</category>
      <category>tutorials</category>
    </item>
    <item>
      <title>Search Is the Most Neglected Feature in Every SaaS App</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Sun, 12 Apr 2026 15:13:42 +0000</pubDate>
      <link>https://dev.to/kencho/search-is-the-most-neglected-feature-in-every-saas-app-4ig5</link>
      <guid>https://dev.to/kencho/search-is-the-most-neglected-feature-in-every-saas-app-4ig5</guid>
      <description>&lt;p&gt;Go to any SaaS app you use daily. Click the search bar. Type something slightly outside the exact phrasing of a menu item or document title. See what happens.&lt;/p&gt;

&lt;p&gt;Nine times out of ten, you'll get nothing useful. Maybe a "no results found" page. Maybe results sorted by creation date instead of relevance. Maybe a list of every record that contains one of the three words you typed.&lt;/p&gt;

&lt;p&gt;This happens on products built by teams of 50+ engineers with millions in funding. The search bar is right there in the UI. It just doesn't work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers Are Bad
&lt;/h2&gt;

&lt;p&gt;Search users are not casual browsers. They're the people who showed up knowing what they want.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;68% of users&lt;/strong&gt; go straight to the search bar when they land on a site&lt;/li&gt;
&lt;li&gt;Search users convert at &lt;strong&gt;2-3x the rate&lt;/strong&gt; of non-search users&lt;/li&gt;
&lt;li&gt;Only &lt;strong&gt;15% of visitors&lt;/strong&gt; use site search, but they generate &lt;strong&gt;45% of total revenue&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Amazon's conversion rate jumps from &lt;strong&gt;2% to 12%&lt;/strong&gt; when search is used&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the other side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;80% of users&lt;/strong&gt; leave because the search results are bad&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;81% of sites&lt;/strong&gt; return irrelevant results for simple two-word queries&lt;/li&gt;
&lt;li&gt;U.S. retailers alone lose roughly &lt;strong&gt;$300 billion per year&lt;/strong&gt; to poor search&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last number sounds made up. It's not. When your highest-intent users hit a dead end, they don't try rephrasing. They leave.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Search Actually Gets Built
&lt;/h2&gt;

&lt;p&gt;Nobody sets out to build bad search. It just kind of happens.&lt;/p&gt;

&lt;p&gt;A product launches with no search. Users ask for it. Someone adds &lt;code&gt;WHERE title ILIKE '%query%'&lt;/code&gt;, maybe with some &lt;code&gt;ts_vector&lt;/code&gt; if they're feeling ambitious. It ships. It technically works.&lt;/p&gt;

&lt;p&gt;Six months later, the data has grown. Results get worse. Users complain. The team acknowledges it's a problem but nobody prioritizes it because search isn't a feature anyone owns. It's nobody's OKR. It doesn't have a PM. It falls into the gap between product, engineering, and data, and it stays there.&lt;/p&gt;

&lt;p&gt;A year in, somebody suggests Elasticsearch. Two weeks of setup, ongoing tuning nobody wants to do, and eventually it's "good enough" and the team moves on.&lt;/p&gt;

&lt;p&gt;Search is a horizontal feature. It touches everything but lives in no team's roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Keyword Search Breaks Down
&lt;/h2&gt;

&lt;p&gt;Most product search is still keyword matching. And keyword matching can only find exact words.&lt;/p&gt;

&lt;p&gt;User types "affordable laptop for coding." Your database has "Developer Workstation Pro — $899." Zero keyword overlap. Nothing returned.&lt;/p&gt;

&lt;p&gt;User types "warm jacket for hiking." You have "Insulated Trail Parka." Nothing.&lt;/p&gt;

&lt;p&gt;User uploads a photo of a product they saw in a store. Keyword search doesn't even know what to do with that.&lt;/p&gt;

&lt;p&gt;This is how normal people search. They describe what they want in their own words. Keyword search needs them to guess the exact words in your database. The mismatch is constant, and it gets worse with scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Good Search Looks Like
&lt;/h2&gt;

&lt;p&gt;Good search is invisible. You type what you're thinking, you get what you meant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It understands intent, not just keywords.&lt;/strong&gt; "Affordable laptop for coding" surfaces budget developer machines even without exact word matches. Semantic search matches meaning, not strings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's fast.&lt;/strong&gt; Every additional second of latency drops conversions by about 7%. Good search returns results in under 200ms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It handles failure gracefully.&lt;/strong&gt; Showing related results instead of a blank "no results" page keeps 56% of users engaged. Most implementations don't bother.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Autocomplete actually helps.&lt;/strong&gt; Autocomplete alone boosts conversions by 24%. Most implementations either skip it or suggest irrelevant completions based on alphabetical order.&lt;/p&gt;

&lt;p&gt;Spotify's search understands moods and genres, not just song titles. Pinterest is essentially a search engine with a visual interface. None of these are &lt;code&gt;LIKE '%query%'&lt;/code&gt; wrappers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Stays Broken
&lt;/h2&gt;

&lt;p&gt;If the data is this clear, why doesn't every team fix their search?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's not a weekend project.&lt;/strong&gt; Good search in 2026 means semantic understanding, which means embeddings, GPU inference, vector storage, and a pipeline to keep it all in sync. For 1M items, the infrastructure alone runs &lt;a href="https://dev.to/blog/what-it-costs-to-search-1m-images"&gt;$740-1,845/month&lt;/a&gt; before you write a line of product code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The maintenance is the real cost.&lt;/strong&gt; Models get updated. Indexes need rebuilding. Sync logic breaks at 3 AM. Teams underestimate the ongoing tax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ROI is hard to sell.&lt;/strong&gt; "Search users convert at 3x" doesn't cleanly translate to "improving search will 3x conversions." The causality is muddy enough that it never wins the prioritization fight against a new feature or a dashboard redesign.&lt;/p&gt;

&lt;p&gt;So search stays in the backlog. The search bar sits there, quietly losing money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Is a Product Surface
&lt;/h2&gt;

&lt;p&gt;The search bar is the only place in your product where users tell you what they want in their own words. Every other interaction is constrained by your UI - buttons you designed, menus you structured, forms you created. Search is where they express raw intent.&lt;/p&gt;

&lt;p&gt;A user who searches is telling you exactly what they need. If you give them the right result, they convert. If you give them nothing, they leave.&lt;/p&gt;

&lt;p&gt;The companies that figured this out early built their entire products around it. The ones that didn't are still shipping &lt;code&gt;ILIKE '%query%'&lt;/code&gt; and wondering why engagement plateaus.&lt;/p&gt;

&lt;p&gt;Search isn't a feature. It's the closest thing to a conversation your product has with your users. Treat it like one.&lt;/p&gt;

</description>
      <category>semanticsearch</category>
    </item>
  </channel>
</rss>
