Search & Discovery¶
SCOPE_ITEM: Search, filtering, ranking, and recommendation systems that help buyers find relevant listings on the marketplace.
Decision Tree¶
IF: Marketplace has <1000 listings at launch. THEN: PostgreSQL full-text search is sufficient for MVP.
IF: Marketplace has >1000 listings or needs typo tolerance / faceted filtering. THEN: Implement Meilisearch (EU-hosted, open source).
IF: Marketplace is location-based (local services, rentals). THEN: Include PostGIS for geospatial queries + distance sorting.
IF: Marketplace needs personalised recommendations. THEN: Include recommendation engine (Phase 2, post-launch).
Full-Text Search¶
PostgreSQL Full-Text Search (MVP)¶
SCOPE_ITEM: Search listings using PostgreSQL tsvector/tsquery.
INCLUDES:
- tsvector column on listings table (title + description combined).
- GIN index for fast full-text lookups.
- ts_rank for relevance scoring.
- Language-specific text search configuration (dutch, english).
- Search input sanitisation (strip special characters, limit length).
-- Add search vector column
ALTER TABLE listings ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('dutch', coalesce(title, '')), 'A') ||
setweight(to_tsvector('dutch', coalesce(description, '')), 'B')
) STORED;
CREATE INDEX idx_listings_search ON listings USING gin(search_vector);
-- Search query
SELECT id, title, ts_rank(search_vector, query) AS rank
FROM listings, plainto_tsquery('dutch', $1) query
WHERE search_vector @@ query
AND status = 'active'
ORDER BY rank DESC
LIMIT 20 OFFSET $2;
CHECK: PostgreSQL FTS does NOT support typo tolerance or fuzzy matching. CHECK: PostgreSQL FTS is sufficient for <1000 listings with exact/stem matches. CHECK: Set search configuration language to match marketplace primary language.
Meilisearch (Recommended for Production)¶
SCOPE_ITEM: Dedicated search engine for fast, typo-tolerant search.
INCLUDES: - Self-hosted Meilisearch instance (EU infrastructure). - Index sync from PostgreSQL (on listing create/update/delete). - Typo tolerance (built-in, configurable distance). - Faceted search (category, price range, location, rating). - Instant search results (<50ms response time). - Highlighted search terms in results. - Stop words and synonyms configuration.
Listing CRUD (PostgreSQL)
└── After insert/update/delete
└── Sync to Meilisearch index (async via BullMQ)
Search request (frontend)
└── Direct to Meilisearch API (read-only public key)
└── Returns: listing IDs + highlights + facet counts
└── Hydrate full listing data from PostgreSQL if needed
OPTIONAL: - Multi-index search (listings + sellers + categories in one query). - Search analytics (popular queries, zero-result queries). - Custom ranking rules (boost verified sellers, boost recent listings). - Geo-search (sort by distance to user location).
CHECK: Meilisearch search API key (public, read-only) can be exposed to frontend. Admin API key must stay server-side. CHECK: Index sync must handle deletions (soft-deleted listings removed from search index). CHECK: Meilisearch max index size: plan for ~10x expected listing count.
Faceted Filtering¶
SCOPE_ITEM: Structured filtering to narrow search results.
Standard Facets¶
INCLUDES: - Category (hierarchical, show subcategories of selected parent). - Price range (min/max input or predefined ranges). - Condition (new, used, refurbished — if applicable). - Rating (minimum seller/listing rating). - Location (province/city — if applicable).
OPTIONAL: - Custom facets per category (e.g., brand, size, colour for fashion). - Date posted (last 24h, last week, last month). - Shipping options (free shipping, same-day delivery). - Seller type (business, individual). - Availability (in stock, pre-order).
Facet Implementation¶
INCLUDES: - Facet counts displayed next to each filter option. - Facet counts update dynamically as filters are applied. - URL-based filter state (shareable filtered URLs). - Mobile-friendly filter UI (bottom sheet or sidebar drawer). - Clear all filters action. - Active filter chips displayed above results.
CHECK: Facet counts must reflect currently active filters (conjunctive within a facet group, disjunctive between facet groups). CHECK: URL query parameters for filters enable SEO crawling of filtered pages and shareable links.
Relevance Ranking¶
SCOPE_ITEM: Order search results by relevance and quality.
Default Ranking Strategy¶
1. Text relevance (search term match quality)
2. Listing quality score (completeness: title, description, images)
3. Seller trust score (verified, rating, dispute rate)
4. Recency (newer listings slightly boosted)
5. Engagement signals (views, saves, conversion rate)
Sort Options (User Selectable)¶
INCLUDES: - Relevance (default for search queries). - Price: low to high. - Price: high to low. - Newest first. - Rating: highest first.
OPTIONAL: - Distance: nearest first (requires user location). - Popularity (most viewed/saved). - Best match (personalised, requires recommendation engine).
Promoted Listings¶
OPTIONAL: SCOPE_ITEM: Paid placement for sellers to boost listing visibility.
INCLUDES: - Promoted listings appear at top of relevant search results. - Clear "Promoted" or "Sponsored" label (EU P2B Regulation requirement). - Cost: per-impression (CPM) or per-click (CPC), configurable. - Budget cap per listing per day. - Performance reporting for seller (impressions, clicks, spend).
COMPLIANCE: EU Platform-to-Business Regulation requires transparency about ranking factors and paid placement. Must disclose that promoted listings receive preferential treatment.
Location-Based Search¶
OPTIONAL: SCOPE_ITEM: Find listings near a specific location.
PostGIS Implementation¶
INCLUDES:
- PostGIS extension on PostgreSQL.
- GEOGRAPHY column on listings or sellers (lat/lng point).
- Distance calculation (ST_DWithin, ST_Distance).
- Sort by distance from user-provided location.
- Radius filter (e.g., within 25 km).
ALTER TABLE listings ADD COLUMN location GEOGRAPHY(POINT, 4326);
CREATE INDEX idx_listings_location ON listings USING GIST(location);
-- Find listings within 25 km of Amsterdam Central
SELECT id, title,
ST_Distance(location, ST_MakePoint(4.8998, 52.3792)::geography) AS distance_m
FROM listings
WHERE ST_DWithin(location, ST_MakePoint(4.8998, 52.3792)::geography, 25000)
AND status = 'active'
ORDER BY distance_m ASC
LIMIT 20;
OPTIONAL: - Map view (Mapbox GL JS or Leaflet + OpenStreetMap tiles). - Cluster markers for dense areas. - Geocoding input (address to lat/lng via Nominatim or Google Geocoding). - Browser geolocation API for "near me" functionality.
CHECK: Use OpenStreetMap-based geocoding (Nominatim) for EU data residency compliance. Google Geocoding transfers data to US.
Recommendations¶
OPTIONAL: SCOPE_ITEM: Personalised listing suggestions for buyers.
Collaborative Filtering (Phase 2)¶
INCLUDES: - "Buyers who viewed this also viewed" (item-item similarity). - Based on view and purchase history. - Computed in batch (daily, BullMQ job). - Stored as precomputed recommendation sets per listing.
Content-Based (Simpler, Phase 1)¶
INCLUDES: - "Similar listings" based on category + attributes. - "More from this seller" on listing detail page. - "Recently viewed" for returning users. - "Popular in [category]" on category browse pages.
CHECK: Recommendation data must be aggregated/anonymised (GDPR). Individual user behaviour data has strict retention limits.
Search UX Patterns¶
Search Bar¶
INCLUDES: - Prominent search bar on homepage and all pages. - Type-ahead suggestions (after 2 characters, debounced 300ms). - Recent searches (stored in localStorage, max 10). - Category scope selector (search within category).
Search Results Page¶
INCLUDES: - Result count displayed. - Grid view (default for products) and list view toggle. - Listing card: image, title, price, seller name, rating, location. - Pagination (offset-based) or infinite scroll (cursor-based). - "No results" state with suggestions (check spelling, broaden filters). - "Did you mean" for typo corrections (Meilisearch built-in).
Browse Pages¶
INCLUDES: - Category browse (hierarchical navigation). - Featured/curated collections on homepage. - New arrivals section.
OPTIONAL: - Trending/popular section. - Seasonal collections. - Editor's picks.
Performance Requirements¶
CHECK: Search results returned in <200ms (Meilisearch) or <500ms (PostgreSQL FTS). CHECK: Facet counts computed in same query (no separate requests). CHECK: Search index sync latency <30 seconds after listing create/update. CHECK: Type-ahead suggestions returned in <100ms. CHECK: Search works correctly with 0 results (empty state, not error).
Scoping Questions¶
CHECK: Expected number of listings at launch and 12-month projection? CHECK: Is typo-tolerant search required? CHECK: Is location-based search/filtering required? CHECK: What facets/filters are needed beyond standard (category, price)? CHECK: Is promoted/sponsored listings a revenue stream? CHECK: Are personalised recommendations desired (Phase 1 or Phase 2)? CHECK: What is the primary marketplace language? CHECK: Is multi-language search needed?