<?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>How to Make Your Catalog Searchable by AI Agents</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Wed, 03 Jun 2026 11:08:02 +0000</pubDate>
      <link>https://dev.to/kencho/how-to-make-your-catalog-searchable-by-ai-agents-49gb</link>
      <guid>https://dev.to/kencho/how-to-make-your-catalog-searchable-by-ai-agents-49gb</guid>
      <description>&lt;p&gt;A new kind of shopper showed up in 2026, and it isn't human. AI agents (the shopping modes in ChatGPT, Perplexity, and a growing list of assistants) now research products, compare options across stores, and in some cases complete the purchase, all without the person ever opening your website.&lt;/p&gt;

&lt;p&gt;This changes what "being findable" means. For years the goal was to rank in Google and convert a human browsing your site. Now there's a second audience: software that reads your catalog programmatically and decides whether your product makes the shortlist. If an agent can't read and search your catalog, your products are invisible to it, no matter how good your SEO is.&lt;/p&gt;

&lt;p&gt;This guide covers what AI agents actually need from your catalog, and how to expose it: structured product data they can read, and a search endpoint they can query.&lt;/p&gt;

&lt;h2&gt;
  
  
  How an AI Shopping Agent Finds Products
&lt;/h2&gt;

&lt;p&gt;Picture a shopper who tells their assistant: "find me a waterproof barrel duffel under $120 for a kayaking trip." The agent doesn't browse. It does something closer to this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reads&lt;/strong&gt; product data from stores it can access, as structured machine-readable records (attributes, price, availability), not rendered HTML.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Searches&lt;/strong&gt; for products matching the intent, by meaning and increasingly by image, not by exact keyword.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ranks and shortlists&lt;/strong&gt; based on how well each product fits the request and how complete the data is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acts&lt;/strong&gt;, either handing the shortlist back to the person or completing the purchase through an API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two of those steps depend entirely on your infrastructure: the agent has to be able to &lt;em&gt;read&lt;/em&gt; your catalog, and it has to be able to &lt;em&gt;search&lt;/em&gt; it. Get those two right and you're in the consideration set. Miss either and you're out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirement 1: Structured Data the Agent Can Read
&lt;/h2&gt;

&lt;p&gt;Agents don't want your product page. They want the facts behind it in a predictable format. That means structured data.&lt;/p&gt;

&lt;p&gt;At minimum, expose for each product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A stable product ID or SKU&lt;/li&gt;
&lt;li&gt;Title and a real description (not marketing fluff, the actual attributes)&lt;/li&gt;
&lt;li&gt;Price and currency&lt;/li&gt;
&lt;li&gt;Availability and stock status&lt;/li&gt;
&lt;li&gt;Key attributes: material, dimensions, color, category, and anything specific to your vertical&lt;/li&gt;
&lt;li&gt;Image URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are two common ways to surface this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema.org markup on your pages.&lt;/strong&gt; Add &lt;code&gt;Product&lt;/code&gt; structured data (JSON-LD) so any agent crawling your site can parse it. This is table stakes and most ecommerce platforms support it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A clean product feed or API.&lt;/strong&gt; A JSON endpoint that returns your catalog as structured records. This is what agents prefer, because it's fast and unambiguous. If you already have a product feed for ads, you're most of the way there.&lt;/p&gt;

&lt;p&gt;The principle is simple: anything an agent has to &lt;em&gt;guess&lt;/em&gt; about your product is a reason to skip it in favor of a competitor whose data is explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirement 2: A Search Endpoint the Agent Can Query
&lt;/h2&gt;

&lt;p&gt;Structured data lets an agent &lt;em&gt;read&lt;/em&gt; your catalog. But for an agent to find the right product out of thousands, it needs to &lt;em&gt;search&lt;/em&gt; it the way it thinks: by intent and by image, not by exact keyword.&lt;/p&gt;

&lt;p&gt;This is where most catalogs fall short. Default store search is keyword-based. An agent's query ("waterproof barrel duffel for kayaking") won't match a product titled "Voyager 60L Roll-Top Bag" through keywords, even though it's a perfect fit. Semantic search closes that gap because it matches meaning. Visual search closes the other half, because agents increasingly pass images ("find products like this photo") rather than text.&lt;/p&gt;

&lt;p&gt;The practical move is to put your catalog behind a search API that handles both. Here's what that looks like with Vecstore.&lt;/p&gt;

&lt;p&gt;Index each product once, carrying both its text and its image so it's findable either way:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(&lt;span class="hl-green"&gt;`${BASE_URL}/databases/${DB_ID}/documents`&lt;/span&gt;, {
  method: &lt;span class="hl-green"&gt;'POST'&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;,
  },
  body: JSON.stringify({
    image_url: &lt;span class="hl-green"&gt;'https://store.com/products/voyager-duffel.jpg'&lt;/span&gt;,
    text: &lt;span class="hl-green"&gt;'Voyager 60L roll-top duffel, waterproof, marine-grade zipper'&lt;/span&gt;,
    metadata: {
      sku: &lt;span class="hl-green"&gt;'BAG-60L'&lt;/span&gt;,
      price: &lt;span class="hl-green"&gt;109&lt;/span&gt;,
      in_stock: &lt;span class="hl-blue"&gt;true&lt;/span&gt;,
      category: &lt;span class="hl-green"&gt;'bags'&lt;/span&gt;,
    },
  }),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now expose a single search endpoint an agent can call with a natural-language query:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;app.post(&lt;span class="hl-green"&gt;'/agent/search'&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 = &lt;span class="hl-green"&gt;10&lt;/span&gt; } = req.body;

  &lt;span class="hl-blue"&gt;const&lt;/span&gt; result = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(&lt;span class="hl-green"&gt;`${BASE_URL}/databases/${DB_ID}/search`&lt;/span&gt;, {
    method: &lt;span class="hl-green"&gt;'POST'&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;,
    },
    body: JSON.stringify({ text: query, top_k }),
  });

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

  &lt;span class="hl-gray"&gt;// Return clean, structured records the agent can act on&lt;/span&gt;
  res.json(results.map((r) =&amp;gt; ({
    sku: r.metadata.sku,
    price: r.metadata.price,
    in_stock: r.metadata.in_stock,
    score: r.score,
  })));
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That's the whole interface an agent needs: send intent, get back structured, ranked products with the metadata required to make a decision. Because the same index also holds image embeddings, the agent can pass an image instead of text and the endpoint works the same way.&lt;/p&gt;

&lt;p&gt;For a deeper walkthrough of the search side, see &lt;a href="https://dev.to/blog/ecommerce-search-api"&gt;Ecommerce Search API: Add Visual and Semantic Search&lt;/a&gt; and &lt;a href="https://dev.to/blog/how-to-add-find-similar-products"&gt;How to Add a "Find Similar Products" Feature&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the Search Itself Agent-Friendly
&lt;/h2&gt;

&lt;p&gt;A few details matter more for agents than for humans:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Return scores, not just results.&lt;/strong&gt; Agents use the similarity score to decide confidence. A human eyeballs a grid; an agent needs the number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep metadata complete and honest.&lt;/strong&gt; Price, stock, and category in every record. An agent that finds a great match with no price will drop it for one that has it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Return structured fields, not HTML.&lt;/strong&gt; The endpoint above returns plain JSON, not a rendered card. That's what an agent can parse and act on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep latency low.&lt;/strong&gt; Agents often run many queries to fulfill one request. A slow endpoint gets timed out and skipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is Worth Doing Now
&lt;/h2&gt;

&lt;p&gt;Agentic commerce is early, which is exactly why it's worth getting ahead of. The stores that are readable and searchable by agents today get included in shortlists while competitors are still serving keyword search and unstructured pages. The work also pays off immediately for your human shoppers, because semantic and visual search convert better than keyword search regardless of who (or what) is searching.&lt;/p&gt;

&lt;p&gt;You don't need to rebuild your store. You need two things: structured product data exposed cleanly, and a search endpoint that understands meaning and images. The first is mostly configuration. The second is an API call.&lt;/p&gt;

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

&lt;p&gt;The next wave of shoppers includes software that reads your catalog and decides whether your products qualify. Making your catalog agent-ready comes down to being readable (structured data) and searchable (semantic and visual search behind a clean API). Do both, and you're in the consideration set for human and agent shoppers alike.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;Try Vecstore for free&lt;/a&gt;. 100 credits on signup, no credit card. Index a sample of your catalog and build an agent-ready search endpoint in an afternoon.&lt;/p&gt;

</description>
      <category>imagesearch</category>
      <category>industries</category>
    </item>
    <item>
      <title>Ecommerce Search API: Add Visual and Semantic Search</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Sat, 30 May 2026 20:50:51 +0000</pubDate>
      <link>https://dev.to/kencho/ecommerce-search-api-add-visual-and-semantic-search-4j7d</link>
      <guid>https://dev.to/kencho/ecommerce-search-api-add-visual-and-semantic-search-4j7d</guid>
      <description>&lt;p&gt;Most online stores ship with search that matches keywords. A shopper types "navy linen shirt," and if your product titles say "indigo cotton-blend henley," they get zero results and leave. The product was right there. The search just didn't understand them.&lt;/p&gt;

&lt;p&gt;An ecommerce search API fixes that. Instead of matching exact words, it understands meaning and visual similarity, so shoppers find products whether they describe them, misspell them, or upload a photo. This guide covers what an ecommerce search API actually does, what to look for when choosing one, and how to add visual and semantic search to your store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Default Store Search Loses Sales
&lt;/h2&gt;

&lt;p&gt;Search is one of the highest-intent actions a shopper takes. People who use site search convert at a much higher rate than people who only browse, because they already know what they want. Which means a bad search bar is leaking your best customers.&lt;/p&gt;

&lt;p&gt;The default search on most platforms (Shopify, WooCommerce, Magento) is keyword-based. It has three predictable failure modes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vocabulary mismatch.&lt;/strong&gt; The shopper's words don't match your product copy. "Couch" versus "sofa," "sneakers" versus "trainers," "winter coat" versus "insulated parka." Keyword search returns nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No visual understanding.&lt;/strong&gt; A shopper sees a dress on Instagram and wants something similar. They can't type their way to it. Without image search, that intent is lost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typos and natural language.&lt;/strong&gt; "Wireless noise canceling headphons under 100" breaks most built-in search bars. A modern search API handles the typo, the price filter, and the natural phrasing.&lt;/p&gt;

&lt;p&gt;Every one of those is a shopper who was ready to buy and couldn't find the product. An ecommerce search API closes that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an Ecommerce Search API Does
&lt;/h2&gt;

&lt;p&gt;There are two capabilities that matter most for stores, and the best APIs offer both:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic (text) search.&lt;/strong&gt; The shopper describes what they want in natural language and the API returns products that match the meaning, not just the keywords. "Affordable laptop for school" surfaces budget student notebooks even if none of them literally say "affordable for school."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual (image) search.&lt;/strong&gt; The shopper uploads or points to a photo and the API returns visually similar products from your catalog. This powers "find similar products," "shop the look," and reverse image lookup. No tagging required: the model understands what the product looks like.&lt;/p&gt;

&lt;p&gt;Underneath, both work the same way. The API converts your products (their text and their images) into vector embeddings, stores them in an index, and at search time finds the closest matches. You don't have to build any of that. You insert products and call a search endpoint.&lt;/p&gt;

&lt;p&gt;For a deeper look at the customer-facing side, see &lt;a href="https://dev.to/blog/visual-search-ecommerce"&gt;Visual Search for Ecommerce&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Look For When Choosing One
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Multimodal in one API.&lt;/strong&gt; You want text search and image search from the same product index, not two separate systems. If adding visual search means standing up a second service, the integration cost doubles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic embedding.&lt;/strong&gt; The API should embed your products for you when you insert them. If you have to generate your own embeddings, you're back to running ML infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency under 200ms.&lt;/strong&gt; Search has to feel instant. Anything slower and shoppers abandon the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing that fits a store.&lt;/strong&gt; Pay-as-you-go per operation is friendly for stores with seasonal or uneven traffic. Watch for APIs that charge per stored vector, which punishes you for having a big catalog even on slow days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Platform fit.&lt;/strong&gt; If you're on Shopify or WooCommerce, check whether there's a clear integration path. The closer it is to a few API calls, the faster you ship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real free tier.&lt;/strong&gt; You should be able to index a sample of your catalog and test relevance on real queries before paying.&lt;/p&gt;

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

&lt;p&gt;The pattern is the same regardless of platform: index your products, then call search. Here is what it looks like with Vecstore's API.&lt;/p&gt;

&lt;p&gt;First, push your catalog into a searchable index. Each product can carry both its image and its text, so the same record is findable by description or by photo:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(&lt;span class="hl-green"&gt;`${BASE_URL}/databases/${DB_ID}/documents`&lt;/span&gt;, {
  method: &lt;span class="hl-green"&gt;'POST'&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;,
  },
  body: JSON.stringify({
    image_url: &lt;span class="hl-green"&gt;'https://store.com/products/navy-henley.jpg'&lt;/span&gt;,
    text: &lt;span class="hl-green"&gt;'Navy cotton-blend henley, slim fit, long sleeve'&lt;/span&gt;,
    metadata: { product_id: &lt;span class="hl-green"&gt;'SKU-1024'&lt;/span&gt;, price: &lt;span class="hl-green"&gt;39&lt;/span&gt; },
  }),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then handle a text search from your store's search bar:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-blue"&gt;const&lt;/span&gt; res = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(&lt;span class="hl-green"&gt;`${BASE_URL}/databases/${DB_ID}/search`&lt;/span&gt;, {
  method: &lt;span class="hl-green"&gt;'POST'&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;,
  },
  body: JSON.stringify({ text: &lt;span class="hl-green"&gt;'navy linen shirt'&lt;/span&gt;, top_k: &lt;span class="hl-green"&gt;12&lt;/span&gt; }),
});

&lt;span class="hl-blue"&gt;const&lt;/span&gt; { results } = &lt;span class="hl-blue"&gt;await&lt;/span&gt; res.json();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To add "find similar products," send a product image instead of text. Same endpoint, same index:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;body: JSON.stringify({
  image_url: &lt;span class="hl-green"&gt;'https://store.com/uploads/shopper-photo.jpg'&lt;/span&gt;,
  top_k: &lt;span class="hl-green"&gt;12&lt;/span&gt;,
}),
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each result returns the product's metadata (your SKU, price, anything you stored) and a similarity score, so you can render them straight into your product grid.&lt;/p&gt;

&lt;p&gt;For platform-specific walkthroughs, see &lt;a href="https://dev.to/blog/how-to-add-image-search-to-shopify"&gt;How to Add Image Search to Shopify&lt;/a&gt; and &lt;a href="https://dev.to/blog/how-to-add-find-similar-products"&gt;How to Add a "Find Similar Products" Feature&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build vs Buy
&lt;/h2&gt;

&lt;p&gt;You can build this yourself. It means choosing an embedding model, running inference on every product image and title, standing up a vector database, building an ingestion pipeline that re-embeds products when they change, and keeping it all running through your traffic peaks.&lt;/p&gt;

&lt;p&gt;That's the right call if search is your core product or you have an ML team with spare capacity. For a store that needs better search as a feature, not as a second product, an API is faster and cheaper to operate. You skip the GPU bills, the vector database ops, and the weeks of pipeline work, and you get a search bar that converts.&lt;/p&gt;

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

&lt;p&gt;Default store search costs you sales every day from shoppers who described a product slightly differently than you did. An ecommerce search API closes that gap with semantic and visual search, and modern ones do it behind a single HTTP call.&lt;/p&gt;

&lt;p&gt;If you also want content moderation on user-uploaded images, or face search, the same API can cover those without a second integration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;Try Vecstore for free&lt;/a&gt;. 100 credits on signup, no credit card. Index a sample of your catalog and test relevance on your own products in an afternoon.&lt;/p&gt;

</description>
      <category>imagesearch</category>
      <category>industries</category>
    </item>
    <item>
      <title>Reverse Image Search API: The Developer's Guide for 2026</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Thu, 28 May 2026 04:45:23 +0000</pubDate>
      <link>https://dev.to/kencho/reverse-image-search-api-the-developers-guide-for-2026-20mg</link>
      <guid>https://dev.to/kencho/reverse-image-search-api-the-developers-guide-for-2026-20mg</guid>
      <description>&lt;p&gt;If you've ever tried to add "find visually similar images" to an app, you've probably hit the same wall: the tutorials assume you want to spend a month training a CLIP model and standing up a vector database. Most teams don't. They want an HTTP endpoint they can POST an image to and get matches back.&lt;/p&gt;

&lt;p&gt;That's what a reverse image search API is. This guide covers what it actually does under the hood, what to look for when choosing one, how the main options compare in 2026, and how to wire it into your app with a few lines of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Reverse Image Search API Does
&lt;/h2&gt;

&lt;p&gt;You send an image. You get back a ranked list of visually similar images from your own catalog (or a public index, depending on the provider). No keywords, no tags, no manual feature extraction.&lt;/p&gt;

&lt;p&gt;Under the hood, every API doing this in 2026 follows the same three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Embed&lt;/strong&gt; the query image using a vision model (usually a CLIP-family or SigLIP-style encoder). The output is a high-dimensional vector that captures what the image &lt;em&gt;looks like&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search&lt;/strong&gt; that vector against an index of previously embedded images using approximate nearest neighbor (ANN) algorithms like HNSW or IVF.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rank&lt;/strong&gt; the closest matches by cosine similarity and return them with a score.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The trick is that you don't have to care about any of those steps. A good API hides all of it behind a single HTTP call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Different Kinds of Reverse Image Search API
&lt;/h2&gt;

&lt;p&gt;This category gets confused a lot, so it's worth being precise. There are two very different things people call a "reverse image search API":&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public web search APIs.&lt;/strong&gt; You send an image, the service searches the public internet for visually similar pages and matches. Examples: TinEye, Google Lens (no official public API), Bing Visual Search, ImageRaider. Useful for copyright enforcement, brand monitoring, and finding the source of an image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private catalog APIs.&lt;/strong&gt; You upload your own images to an index, then search against that index. The service never looks at the public web. Examples: Vecstore, Algolia Recommend, Imagga, plus self-hosted stacks built on Pinecone or Weaviate. This is what powers "find similar products" in e-commerce, duplicate detection in marketplaces, and "more like this" in stock photo sites.&lt;/p&gt;

&lt;p&gt;Most developers Googling "reverse image search API" actually want the second one. They have a product catalog or a media library and they want users to search inside it visually. If that's you, the rest of this guide is aimed at you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Look For When Picking One
&lt;/h2&gt;

&lt;p&gt;Not all reverse image search APIs are built the same. Here's what actually matters in production:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Indexing model.&lt;/strong&gt; Does the API embed the image for you, or do you have to provide your own embeddings? The first is far simpler. The second gives you control but means you're back to running inference yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency.&lt;/strong&gt; A typical search call should return in well under 200ms for catalogs up to a few million images. Anything above 500ms will start to feel broken in a real product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing model.&lt;/strong&gt; Per-operation pricing scales with usage and is friendly when you're small. Per-vector storage pricing punishes you for having a big catalog even if traffic is low. Some APIs charge for both, which gets expensive fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multimodal search.&lt;/strong&gt; Can the same index also handle text-to-image search, OCR search, or face search? If yes, you avoid running multiple systems for related features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free tier.&lt;/strong&gt; You should be able to test the API end-to-end on real images without entering a credit card. If the free tier is too small to load a real catalog into, the vendor is hiding the experience from you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-host option.&lt;/strong&gt; Most teams don't need it, but if your data can't leave your VPC, it matters. Few hosted APIs offer a self-hosted tier; most of the cheaper "self-host" options are really do-it-yourself stacks (pgvector, Qdrant, Weaviate) where you build the API yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Main Options Compare in 2026
&lt;/h2&gt;

&lt;p&gt;Quick rundown of the reverse image search APIs developers actually evaluate this year. Pricing is from each vendor's public page at the time of writing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vecstore.&lt;/strong&gt; Single REST API for reverse image search, text-to-image search, OCR search, face search, and NSFW detection. Embedding is automatic on insert. Free tier with 100 credits, no credit card. Pay-as-you-go from $1.60 per 1K operations. Good fit for product teams that want one API for all visual search features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imagga.&lt;/strong&gt; Image recognition platform with a similar-images endpoint as one piece of a broader catalog. Strong on tagging and color analysis. Pricing starts at a fixed monthly plan, which can be cheaper at higher volume but worse at low volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Algolia (Recommend / Visual Search).&lt;/strong&gt; Mature search platform with visual similarity built as part of their broader product. Strong on relevance tuning. Pricing starts to add up fast once you cross their free tier; aimed at larger teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TinEye / ImageRaider.&lt;/strong&gt; Public-web reverse search. If you want to find where an image appears across the internet (copyright, brand protection), this is the category. Not the right tool for searching your own catalog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pinecone / Weaviate / Qdrant + CLIP.&lt;/strong&gt; Not an API in the same sense. You provide the embeddings (run a CLIP model yourself), they provide the vector index. Maximum control, maximum operational work. Reasonable choice if you have an ML team. Overkill if you just want similar-product search on a Shopify store.&lt;/p&gt;

&lt;p&gt;For a deeper side-by-side on the vector database options, see &lt;a href="https://dev.to/blog/vector-database-performance-compared"&gt;pgvector vs Pinecone vs Qdrant benchmarks&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Working Example in 12 Lines
&lt;/h2&gt;

&lt;p&gt;Here's what calling a reverse image search API looks like in practice. This is Vecstore's API; most modern APIs in this category look similar.&lt;/p&gt;

&lt;p&gt;First, insert images into your catalog. Each one is automatically embedded:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(&lt;span class="hl-green"&gt;`${BASE_URL}/databases/${DB_ID}/documents`&lt;/span&gt;, {
  method: &lt;span class="hl-green"&gt;'POST'&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;,
  },
  body: JSON.stringify({ image_url: &lt;span class="hl-green"&gt;'https://example.com/product-001.jpg'&lt;/span&gt; }),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then search by sending a query image:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hl-blue"&gt;const&lt;/span&gt; res = &lt;span class="hl-blue"&gt;await&lt;/span&gt; fetch(&lt;span class="hl-green"&gt;`${BASE_URL}/databases/${DB_ID}/search`&lt;/span&gt;, {
  method: &lt;span class="hl-green"&gt;'POST'&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;,
  },
  body: JSON.stringify({ image_url: &lt;span class="hl-green"&gt;'https://example.com/query.jpg'&lt;/span&gt;, top_k: &lt;span class="hl-green"&gt;10&lt;/span&gt; }),
});

&lt;span class="hl-blue"&gt;const&lt;/span&gt; { results } = &lt;span class="hl-blue"&gt;await&lt;/span&gt; res.json();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each result includes the matching image's ID and a similarity score between 0 and 1. Scores above 0.9 are near-duplicates; 0.7 to 0.9 is "clearly related"; below 0.5 starts to get noisy. Pick a threshold that fits your use case.&lt;/p&gt;

&lt;p&gt;For a full walkthrough including an Express server with image uploads, see &lt;a href="https://dev.to/blog/how-to-build-reverse-image-search"&gt;How to Build a Reverse Image Search Engine with JavaScript&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is There a Free Reverse Image Search API?
&lt;/h2&gt;

&lt;p&gt;Sort of. A few things people mean when they search this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free tier on a paid API.&lt;/strong&gt; Most hosted providers (Vecstore included) give you a free quota every month so you can build, test, and run small projects without paying. This is the path most developers actually want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open-source self-hosted.&lt;/strong&gt; You can build your own reverse image search using CLIP and pgvector or Qdrant for $0 in software cost. You'll pay in server time, GPU bills for inference, and engineering hours. It's "free" the way running your own database is "free."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Truly free hosted APIs.&lt;/strong&gt; A few academic and demo APIs exist (mostly built on top of CLIP and a tiny index). They tend to be rate-limited, unreliable, and not built for production. Fine for a hackathon. Don't ship on them.&lt;/p&gt;

&lt;p&gt;If you just want to try the API category without committing, a free tier on a production-grade API is the realistic path. Vecstore's free tier gives you 100 credits with no credit card.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You Don't Need an API at All
&lt;/h2&gt;

&lt;p&gt;A reverse image search API solves a specific class of problems well: visual similarity at scale, with low latency, across a catalog of thousands to millions of images. If your problem is smaller than that, you have simpler options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Under 1,000 images.&lt;/strong&gt; A perceptual hash (pHash) library plus a SQL &lt;code&gt;LIKE&lt;/code&gt; query on hash strings can be enough for duplicate detection or near-duplicate matching. No API needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exact-match only.&lt;/strong&gt; If you just need to know when the exact same file has been uploaded twice, SHA-256 of the file bytes does it for free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tag-based search.&lt;/strong&gt; If your "visual search" is really "find products tagged as red shoes," that's a normal database query plus a tagging step. You don't need vectors at all.&lt;/p&gt;

&lt;p&gt;The API category earns its keep when you need to match images that are &lt;em&gt;similar but not identical&lt;/em&gt; (rotated, cropped, recolored, different angle of the same object) and your catalog is too big to compare every pair manually.&lt;/p&gt;

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

&lt;p&gt;A reverse image search API in 2026 is a commodity in the same way that "send an email API" is. The interesting question isn't whether to build the embedding pipeline yourself (you shouldn't, unless you're a search vendor). It's which provider fits your stack, your pricing model, and the rest of your visual search needs.&lt;/p&gt;

&lt;p&gt;If you also want text-to-image search, OCR, face search, or NSFW detection in the same product, picking one API that does all of them saves you from running several services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vecstore.app/auth/register" rel="noopener noreferrer"&gt;Try Vecstore for free&lt;/a&gt;. 100 credits on signup, no credit card. Build a working reverse image search in an afternoon.&lt;/p&gt;

</description>
      <category>imagesearch</category>
      <category>tutorials</category>
    </item>
    <item>
      <title>Fashion Visual Search: A Practical Guide for E-Commerce Teams</title>
      <dc:creator>Giorgi</dc:creator>
      <pubDate>Sat, 23 May 2026 10:43:50 +0000</pubDate>
      <link>https://dev.to/kencho/fashion-visual-search-a-practical-guide-for-e-commerce-teams-1l2c</link>
      <guid>https://dev.to/kencho/fashion-visual-search-a-practical-guide-for-e-commerce-teams-1l2c</guid>
      <description>&lt;p&gt;Fashion is the worst category for text search. Shoppers don't know the right words. "That kind of dress my friend wore last summer" isn't a query you can index. "Floral midi with puff sleeves" assumes the shopper knows the vocabulary, and most don't.&lt;/p&gt;

&lt;p&gt;Visual search fixes this. A shopper uploads a photo, screenshots a TikTok, or points their camera at a friend's outfit, and your store returns the closest matches from your catalog. No keywords. No filters. Just "show me this."&lt;/p&gt;

&lt;p&gt;This is not a futuristic feature anymore. It's standard in major fashion apps and a clear differentiator for everyone else. This post walks through what visual search actually does, where it works, where it doesn't, and how to ship it without hiring an ML team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Text Search Loses in Fashion
&lt;/h2&gt;

&lt;p&gt;Fashion is visual by nature. A "blue cotton shirt" can mean a hundred different products, and most shoppers don't have the words to narrow it down. They know what it looks like. They don't know what it's called.&lt;/p&gt;

&lt;p&gt;The result is the worst session pattern in e-commerce: shopper searches, gets 200 unrelated results, scrolls, gives up. The product was in the catalog. The shopper was ready to buy. The search failed.&lt;/p&gt;

&lt;p&gt;Filters don't fix this. You can offer color, fit, neckline, sleeve length, occasion, season, brand. Shoppers don't know they want a "boatneck" until they see one. Filters work for shoppers who already know what they want. The high-value shoppers in fashion are the ones who don't.&lt;/p&gt;

&lt;p&gt;Visual search bypasses the entire language problem. Pixels in, products out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Visual Search Actually Does
&lt;/h2&gt;

&lt;p&gt;There are a few distinct features that all get called "visual search." Worth separating them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reverse image search.&lt;/strong&gt; Shopper uploads a photo, you return the closest products in your catalog. The classic use case. Works for finding a specific item or anything that looks like it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Camera search.&lt;/strong&gt; Same idea, but live from the phone camera. Shopper points at a real-world item and your app shows similar products in your store. Heavy in mobile apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shop the look.&lt;/strong&gt; Shopper uploads an outfit photo, you detect each item (shirt, pants, shoes) and return matches for each one separately. Higher complexity, higher conversion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual similarity on PDPs.&lt;/strong&gt; "Similar to this product." A 6-item carousel of visually close products on every product page. Lifts AOV without changing the rest of the site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text-to-image search.&lt;/strong&gt; Shopper types a description, you return visually matching products even when the description doesn't match any titles. "Red dress with thin straps" returns the right red dresses even if none have those exact words.&lt;/p&gt;

&lt;p&gt;For most fashion stores, the lowest-effort highest-impact starting point is &lt;strong&gt;visual similarity on PDPs&lt;/strong&gt;. It runs on every product page, lifts session depth, and doesn't change the rest of the site UX.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Visual Search Doesn't Do
&lt;/h2&gt;

&lt;p&gt;A reality check before anyone builds this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's not magic on bad photos.&lt;/strong&gt; Phone screenshots, dark lighting, busy backgrounds. Quality degrades. The good systems handle most of this, but extreme cases still fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It doesn't read intent.&lt;/strong&gt; A shopper might upload a yellow dress and want any dress shape, not specifically yellow. Or want any color, not that specific yellow. Visual systems return the closest visual match. Without explicit filters, intent is ambiguous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It doesn't replace search, it adds to it.&lt;/strong&gt; Visual search wins on discovery and "shop the look" use cases. Text search still wins for direct queries ("nike air force 1 size 9"). Most fashion stores need both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Catalog quality is everything.&lt;/strong&gt; Bad product photos in your catalog mean bad results. Multiple background colors, inconsistent crop, missing angles. Visual search amplifies whatever you have. Clean catalogs win.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Visual Search Actually Works
&lt;/h2&gt;

&lt;p&gt;Behind the buzzword, it's straightforward.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every product image is converted to an embedding (a list of numbers that represents the visual content).&lt;/li&gt;
&lt;li&gt;All embeddings are stored in a database optimized for similarity search.&lt;/li&gt;
&lt;li&gt;A shopper's uploaded image is converted to the same kind of embedding.&lt;/li&gt;
&lt;li&gt;The system finds the closest stored embeddings and returns the matching products.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The math is "find nearest neighbors in high-dimensional space." The model doing the embedding is usually CLIP or a CLIP variant, trained to understand both images and text in the same space (which is why text-to-image search works too).&lt;/p&gt;

&lt;p&gt;If you're building this from scratch, you need: a CLIP-style model running on GPU, an inference pipeline for new product uploads, a vector database for storage and search, and the glue code to keep your catalog and embeddings in sync.&lt;/p&gt;

&lt;p&gt;If you're using a managed search API, you upload product images and call the search endpoint. The model, the pipeline, the database, and the sync are all hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Build Decision: From Scratch vs Managed API
&lt;/h2&gt;

&lt;p&gt;The honest cost breakdown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building from scratch.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You'll need an ML engineer (or a backend engineer who can pretend to be one). You'll need GPU inference (either self-hosted with serving infra, or via a hosted model API). You'll need a vector database. You'll need an embedding pipeline that processes new products on upload, re-processes when images change, and handles failures cleanly.&lt;/p&gt;

&lt;p&gt;Realistic timeline for a small team: 4-8 weeks to a working version, then ongoing maintenance forever. Cost: GPU compute, vector DB hosting, engineering time, and the opportunity cost of not shipping other things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Managed search API.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You insert product images into the API. You call the search endpoint with a query image. You get matching product IDs back. That's the integration.&lt;/p&gt;

&lt;p&gt;With Vecstore, that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Upload a product&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.vecstore.app/databases/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;dbId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/records`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;in_stock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inStock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Visual search by image&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.vecstore.app/databases/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;dbId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/search`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uploadedImageUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;in_stock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Timeline for a small team: a weekend. Maintenance: none.&lt;/p&gt;

&lt;p&gt;The from-scratch route makes sense if you're at scale and have an ML team. For everyone else, the managed route is the obvious pick.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Track to Make Visual Search Better
&lt;/h2&gt;

&lt;p&gt;Don't ship and forget. The metrics that matter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CTR on visual search results.&lt;/strong&gt; Of users who run a visual search, what percentage click a result? Below 30% means the model isn't finding their intent. Look at query examples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conversion from visual search sessions.&lt;/strong&gt; Sessions that include a visual search query convert at higher rates than text-only sessions. Track this and report it. It's how you justify continued investment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Catalog coverage.&lt;/strong&gt; What percentage of your catalog has appeared in visual search results in the last 30 days? Low coverage means visual search is recirculating the same hits and missing your long tail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stock-out rate in results.&lt;/strong&gt; If 20% of results are out of stock, you're killing trust. Filter at query time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bounce rate by query type.&lt;/strong&gt; Uploaded images vs camera vs text-to-image. If one query type bounces hard, the UX or the model is failing for that flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  UX Patterns That Work
&lt;/h2&gt;

&lt;p&gt;Some patterns that consistently outperform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Camera icon in the search bar.&lt;/strong&gt; Don't bury it. Camera icon in the main search input on mobile drives 5-10x more visual searches than a separate page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drag and drop on desktop.&lt;/strong&gt; Shoppers screenshot Instagram. Make it dead simple to drop that into your search.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Shop this" on user-generated content.&lt;/strong&gt; If you have customer photos, lookbooks, or social embeds, make every image searchable. Highest-converting visual searches start from inspiration content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual similarity on every PDP.&lt;/strong&gt; A "Similar styles" carousel below the fold lifts AOV and recovers shoppers who don't love the current product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outfit completion.&lt;/strong&gt; Shopper looks at a top, you suggest visually-matching bottoms and shoes. The shop-the-look concept on a single product.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next for Visual Search
&lt;/h2&gt;

&lt;p&gt;Two patterns are coming fast in fashion:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Style transfer queries.&lt;/strong&gt; "Show me this dress but in a different cut." The shopper uploads one item and modifies the query in natural language. Mixed visual + text queries are increasingly common.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outfit-as-query.&lt;/strong&gt; Upload a full outfit photo, get a basket of products that recreate the look. Going from "find one item" to "find an outfit" is the next frontier.&lt;/p&gt;

&lt;p&gt;You don't need to ship these to get value from visual search today. The basics (reverse image search, visual similarity on PDPs, text-to-image search) are enough to materially change discovery and conversion in fashion. Start there.&lt;/p&gt;

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

&lt;p&gt;Fashion shoppers describe what they want in pictures. Your search needs to accept that input or you're losing the high-intent half of your traffic.&lt;/p&gt;

&lt;p&gt;Visual search used to be a multi-month engineering project. It isn't anymore. A managed search API gives you reverse image search, text-to-image search, and visual similarity from one endpoint, with no model to train and no pipeline to maintain.&lt;/p&gt;

&lt;p&gt;If your store sells fashion and you're still text-only, you're leaving conversion on the table. Visual search is the lowest-effort, highest-impact upgrade in the category.&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>industries</category>
      <category>imagesearch</category>
    </item>
    <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>
  </channel>
</rss>
