Skip to main content

Search Module

The sub‑50ms, RAM‑based discovery engine – typo‑tolerant, faceted, and built on Typesense.

Typesense Typo Tolerance Facets


📖 Table of Contents


Base Route : /api/v1/search

The search endpoint is public (no authentication required) and is rate‑limited to protect the Typesense RAM cluster.


Endpoint Overview

MethodEndpointAccessDescription
GET/PublicExecute typo‑tolerant, faceted product search against the Typesense index

Rate limit: standardLimiter (100 requests per 15 minutes per IP). See Rate Limiting Guide.


Endpoint Details

GET /

Search for products using Typesense. The index is kept in‑sync with MongoDB via event‑driven sync and a BullMQ retry queue for eventual consistency.

Query parameters: See Query Parameters below.

Example request (typo‑tolerant):

GET /api/v1/search?q=sre&itemType=BANGLE&limit=20

Example request (wildcard – all products):

GET /api/v1/search?q=*

Response (200 OK):

{
"success": true,
"statusCode": 200,
"message": "Search results retrieved successfully",
"data": {
"hits": [
{
"id": "64a7b...",
"name": "Bridal Glass Bangle Set",
"sku": "BAN-001",
"description": "Beautiful red glass bangles...",
"basePrice": 450,
"itemType": "BANGLE",
"mainCategory": "Bangles",
"images": ["https://res.cloudinary.com/..."],
"tags": ["bridal", "glass", "bangle"]
}
],
"facet_counts": {
"itemType": [
{ "value": "BANGLE", "count": 124 },
{ "value": "APPAREL", "count": 87 }
],
"mainCategory": [
{ "value": "Bangles", "count": 124 },
{ "value": "Sarees", "count": 45 }
]
},
"meta": {
"found": 124,
"page": 1,
"limit": 20,
"search_time_ms": 12
}
},
"timestamp": "2026-05-22T10:30:00.000Z"
}

Query Parameters

ParamTypeDefaultMaxDescription
qstring"*"Search query. * returns all products. Typos allowed (up to 2).
pageinteger1Page number (1‑based)
limitinteger20100Items per page (hard cap to prevent memory exhaustion)
itemTypestringFilter by product type (BANGLE, APPAREL, etc.)
mainCategorystringFilter by main category
minPricenumberMinimum base price (inclusive)
maxPricenumberMaximum base price (inclusive)
sortBystring_text_match:descbasePrice:asc, basePrice:desc, createdAt:desc

Important notes:

  • All string filters are exact matches (not partial).
  • Price range filters are applied before sorting.
  • The q parameter with * returns all products (useful for browsing).

Validation Rules (Zod)

The SearchQuerySchema in search.dto.ts enforces:

FieldRules
qstring, optional (default "*")
pageinteger ≥ 1, default 1
limitinteger between 1 and 100, default 20
itemTypeoptional, string
mainCategoryoptional, string
minPriceoptional, number ≥ 0
maxPriceoptional, number ≥ 0
sortByoptional, enum ["basePrice:asc", "basePrice:desc", "createdAt:desc"]

Coercion: All numeric parameters are coerced using z.coerce.number(). Invalid values (e.g., NaN, negative) are rejected.

The schema uses .strict() – extra query parameters are silently ignored (not rejected) to allow future extensions.


Response Structure

FieldTypeDescription
hitsarrayArray of matching products (only indexed fields)
hits[].idstringMongoDB ObjectId (as string)
hits[].namestringProduct name
hits[].skustringUnique SKU
hits[].descriptionstringProduct description
hits[].basePricenumberCurrent price
hits[].itemTypestringBANGLE, APPAREL, etc.
hits[].mainCategorystringCategory for faceting
hits[].imagesarrayCloudinary URLs (index: false – not searchable)
facet_countsobjectCounts per facet field (for UI filters)
meta.foundnumberTotal number of matching products
meta.pagenumberCurrent page
meta.limitnumberItems per page
meta.search_time_msnumberTypesense query time in milliseconds

The images field is index: false in Typesense – it is returned but not searchable. Deeply nested variant data is omitted to preserve RAM.


Error Responses

StatusCodeExample messageWhen
400BAD_REQUEST"limit must be between 1 and 100"Zod validation fails
400BAD_REQUEST"minPrice must be a non-negative number"Invalid price filter
429TOO_MANY_REQUESTS"Too many requests, please try again later."Rate limit exceeded
500INTERNAL_SERVER_ERROR"Search engine temporarily unavailable"Typesense cluster unreachable

If Typesense is down, the API returns 500 (not fallback to MongoDB) because the product catalog is too large for ad‑hoc text search. The health endpoint can be used to monitor Typesense availability.


Runbook

For manual testing with Thunder Client / Postman, see ../../testing/search-runbook.md. The runbook covers:

  • Wildcard search (q=*)
  • Typo‑tolerant search (q=sre for "saree")
  • Faceted filtering (itemType=BANGLE&minPrice=500)
  • Pagination and sorting
  • Edge cases: invalid limit, negative price, NoSQL injection attempts

Synchronisation Architecture (Admin / Background)

Product changes (create, update, delete, stock update) are automatically synced to Typesense via:

  • Inline sync – On product save, the ProductService attempts to index/update the document in Typesense.
  • BullMQ retry queue – If Typesense is temporarily unreachable, the operation is queued in search-sync-queue with exponential backoff (10 retries).
  • Dead letter queue – After 10 failures, the job is marked failed and logged for manual intervention.

This ensures eventual consistency without blocking the admin HTTP response.



Lightning fast, typo‑forgiving – the Reshma‑Core search engine.