# How Image Search Functionality Works in MiniSearch: A Complete Technical Guide

> Explore MiniSearch image search functionality. Discover its type-safe client-server pipeline querying SearXNG, reranking, fetching thumbnails, and caching in IndexedDB.

- Repository: [Victor Nogueira/minisearch](https://github.com/felladrin/minisearch)
- Tags: technical-guide
- Published: 2026-03-01

---

**MiniSearch implements image search functionality as a type-safe client-server pipeline that queries a local SearXNG instance, optionally reranks results, fetches thumbnails as base64 data URLs, and caches responses in IndexedDB.**

The `felladrin/minisearch` repository delivers a complete image search functionality designed for privacy and performance. The system bridges client-side JavaScript with server-side TypeScript handlers to fetch, process, and cache image results from a local SearXNG metasearch engine without exposing user queries to external APIs directly.

## Client-Side Entry Point

The public API for image search functionality begins in [`client/modules/search.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/search.ts) with the `searchImages` function:

```typescript
// client/modules/search.ts
export const searchImages = searchService.searchImages.bind(searchService);

```

This method constructs a request to the `/search/images` endpoint, appending the query string, a token hash, and an optional result limit. The underlying `performSearch` implementation (lines 51‑73) validates the endpoint configuration, enforces query length constraints, and initializes an `AbortController` with a **30-second timeout** to prevent hanging requests. After parsing the JSON response, results flow through the caching layer before returning to the caller.

## Server Endpoint Handling

All image search requests route through the middleware defined in [`server/searchEndpointServerHook.ts`](https://github.com/felladrin/minisearch/blob/main/server/searchEndpointServerHook.ts):

```typescript
// server/searchEndpointServerHook.ts
if (request.url?.startsWith("/search/")) { … }

```

The handler extracts three parameters from the URL: `q` (query), `token` (access token), and `limit`. It first validates the token via `handleTokenVerification`, then determines the search type. For image searches (identified by `!isTextSearch`), it invokes `fetchSearXNG(query, "images", limit)` to retrieve raw results from the configured backend.

## Querying the SearXNG Backend

The server communicates with a local SearXNG instance through [`server/webSearchService.ts`](https://github.com/felladrin/minisearch/blob/main/server/webSearchService.ts):

```typescript
// server/webSearchService.ts
type SearchType = "text" | "images";
...
const searchUrl = buildSearchUrl(query, searchType);
const data = await response.json() as SearxngSearchResponse;
return Array.isArray(data.results) ? data.results : [];

```

The `SearchType` union (line 14) distinguishes between text and image queries. The `buildSearchUrl` function appends the `categories` parameter—setting it to `"images,videos"` when `searchType` equals `"images"`—and targets the local SearXNG endpoint at `http://127.0.0.1:8888/search`. The `performSearch` function (lines 86‑101) executes the HTTP request and validates the response structure before returning the raw result array.

## Result Processing and Normalization

Raw SearXNG responses undergo transformation via `processGraphicalResult` in [`server/webSearchService.ts`](https://github.com/felladrin/minisearch/blob/main/server/webSearchService.ts):

```typescript
// server/webSearchService.ts
async function processGraphicalResult(result: SearxngSearchResult) {
  const thumbnailSource = result.category === "videos" 
    ? result.thumbnail 
    : result.thumbnail_src;
  const sourceUrl = result.category === "videos"
    ? result.iframe_src || result.url
    : result.img_src;

  return [result.title, result.url, thumbnailSource, sourceUrl];
}

```

This function (lines 46‑61) handles the **ImageSearchResult** tuple format: `[title, url, thumbnailSource, sourceUrl]`. It normalizes differences between video and image entries by selecting the appropriate thumbnail and source properties. The `fetchSearXNG` function deduplicates results and maps each entry through this processor, yielding a clean array of image tuples ready for ranking and thumbnail fetching.

## Optional Reranking

Before thumbnail generation, results may pass through the reranker service. The `handleRanking` function (lines 52‑73 in [`server/searchEndpointServerHook.ts`](https://github.com/felladrin/minisearch/blob/main/server/searchEndpointServerHook.ts)) checks the reranker service health. If available, it forwards results to `rankSearchResults` for relevance scoring; otherwise, it returns the unranked list immediately. This optional step allows the image search functionality to prioritize results based on semantic relevance without blocking the pipeline if the service is unavailable.

## Thumbnail Fetching and Data URL Conversion

The server fetches actual thumbnail images and embeds them directly in the JSON response to avoid cross-origin issues in the browser:

```typescript
// server/searchEndpointServerHook.ts
async function fetchThumbnailAsDataUrl(thumbnailSource: string) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), THUMBNAIL_TIMEOUT_MS);
  const response = await fetch(thumbnailSource, { signal: controller.signal });
  const contentType = response.headers.get("content-type") ?? "application/octet-stream";
  const arrayBuffer = await response.arrayBuffer();
  const base64 = Buffer.from(arrayBuffer).toString("base64");
  return `data:${contentType};base64,${base64}`;
}

```

This helper (lines 21‑40) downloads each thumbnail image, converts the binary data to a base64-encoded **data URL**, and substitutes the raw thumbnail source with the embedded string. The final JSON payload sent to the client contains fully self-contained image data that renders immediately without additional network requests.

## Client-Side Caching

Results are persisted in the browser using IndexedDB via the `SearchCacheDatabase` class in [`client/modules/search.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/search.ts). The `executeCachedSearch` wrapper (lines 64‑104) manages the `imageSearchHistory` table, checking for cached entries before making server requests. It tracks cache hit/miss metrics, enforces TTL (time-to-live) expiration, and prunes old entries automatically. This caching layer ensures that repeated queries return instantly while reducing load on the SearXNG backend.

## Implementation Example

To perform an image search in a MiniSearch client application:

```typescript
import { searchImages } from "./client/modules/search";

async function demoImageSearch() {
  // Search for "sunset" and limit to 10 results
  const results = await searchImages("sunset", 10);
  
  // Results type: ImageSearchResult[] = [title, url, thumbnailDataUrl, sourceUrl]
  results.forEach(([title, url, thumbnail, source]) => {
    console.log("Title:", title);
    console.log("Page URL:", url);
    console.log("Embedded Thumbnail:", thumbnail);
    console.log("Source:", source);
  });
}

demoImageSearch();

```

Behind the scenes, `searchImages` hashes the query for cache keys, checks the IndexedDB cache, conditionally fetches from `/search/images`, and publishes the final results through `imageSearchResultsPubSub` (defined in [`client/modules/pubSub.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/pubSub.ts)) for reactive UI updates.

## Summary

- **Client entry**: The `searchImages` function in [`client/modules/search.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/search.ts) provides the public API with built-in timeout handling.
- **Server routing**: [`server/searchEndpointServerHook.ts`](https://github.com/felladrin/minisearch/blob/main/server/searchEndpointServerHook.ts) validates tokens and routes requests to the SearXNG service.
- **Backend query**: [`server/webSearchService.ts`](https://github.com/felladrin/minisearch/blob/main/server/webSearchService.ts) constructs category-specific URLs and normalizes raw results into `[title, url, thumbnail, source]` tuples.
- **Reranking**: Optional relevance ranking via `handleRanking` improves result quality when the service is available.
- **Thumbnail embedding**: `fetchThumbnailAsDataUrl` converts remote images to base64 data URLs for immediate browser rendering.
- **Caching**: IndexedDB storage via `SearchCacheDatabase` eliminates redundant network requests and supports offline access to recent searches.

## Frequently Asked Questions

### How does MiniSearch authenticate image search requests?

MiniSearch protects the image search functionality through token-based authentication implemented in [`server/handleTokenVerification.ts`](https://github.com/felladrin/minisearch/blob/main/server/handleTokenVerification.ts). Every request to `/search/images` must include a valid `token` parameter in the query string. The server verifies this token before querying the SearXNG backend, ensuring that only authorized clients can consume search resources.

### What format does MiniSearch use for image search results?

The system returns results as an array of **ImageSearchResult** tuples defined in [`client/modules/types.ts`](https://github.com/felladrin/minisearch/blob/main/client/modules/types.ts). Each tuple contains four elements: the image title (string), the destination page URL (string), the thumbnail as a base64 data URL (string), and the direct image source URL (string). This format allows the UI to display thumbnails immediately while providing links to both the original image and the hosting page.

### Which search engine backend powers the image search functionality?

MiniSearch relies on a **local SearXNG instance** as its image search backend. The `buildSearchUrl` function in [`server/webSearchService.ts`](https://github.com/felladrin/minisearch/blob/main/server/webSearchService.ts) constructs requests to `http://127.0.0.1:8888/search`, appending the `categories=images,videos` parameter to retrieve graphical content. This self-hosted approach keeps search queries private and eliminates reliance on commercial APIs with rate limits.

### How does MiniSearch handle thumbnail loading errors?

The thumbnail fetching mechanism includes defensive timeout handling using `AbortController` with a configurable `THUMBNAIL_TIMEOUT_MS`. If a thumbnail fails to download or times out, the error propagates up to the main handler, which can skip the problematic result or return the metadata without the embedded image. The client-side cache also stores successful results, reducing the likelihood of repeated failed requests for the same images.