@rohanadnan1/search-service
v0.0.2
Published
Plug-and-play NestJS search service with fuzzy, autocomplete, hybrid, and semantic vector search for MongoDB Atlas.
Readme
@rohanadnan1/search-service
Plug-and-play NestJS search service with fuzzy, autocomplete, hybrid, and semantic vector search for MongoDB Atlas.
Features
- 🔍 Text Search — Full-text search using MongoDB
$textoperator - 🪄 Fuzzy Search — Typo-tolerant search via MongoDB Atlas Search
- ⚡ Autocomplete — Prefix-based suggestions as the user types
- 🔗 Hybrid Search — Combines fuzzy + text with automatic fallback
- 🧠 Vector / Semantic Search — AI-powered meaning-based search using Cohere embeddings
- 📦 Plug-and-Play — One import, one line of config
- 🏗️ Multi-Tenant Ready — API key-based collection isolation
Quick Start (3 Steps)
1. Install
npm install @rohanadnan1/search-servicePeer dependencies — your NestJS project should already have these installed. If not:
npm install @nestjs/common @nestjs/core @nestjs/config @nestjs/mongoose mongoose reflect-metadata rxjsFor vector/semantic search, also install:
npm install cohere-ai
2. Import the Module
In your app.module.ts (or any NestJS module):
import { Module } from '@nestjs/common';
import { SearchServiceModule } from '@rohanadnan1/search-service';
@Module({
imports: [
SearchServiceModule.forRoot({
mongoUri: 'mongodb+srv://user:[email protected]/mydb',
cohereApiKey: 'your-cohere-api-key', // optional — only needed for vector search
}),
],
})
export class AppModule {}That's it. All search services, repositories, and controllers are now available in your app.
3. Use the Services
Inject any service you need:
import { Injectable } from '@nestjs/common';
import {
SearchOrchestratorService,
CollectionsService,
EmbeddingsService,
SearchType,
} from '@rohanadnan1/search-service';
@Injectable()
export class ProductSearchService {
constructor(
private readonly search: SearchOrchestratorService,
private readonly collections: CollectionsService,
private readonly embeddings: EmbeddingsService, // optional
) {}
// Fuzzy search with typo tolerance
async searchProducts(query: string) {
return this.search.search({
collectionName: 'products',
query,
searchType: SearchType.FUZZY,
pagination: { limit: 20, page: 1 },
});
}
// Autocomplete suggestions
async autocomplete(query: string) {
return this.search.search({
collectionName: 'products',
query,
searchType: SearchType.AUTOCOMPLETE,
pagination: { limit: 5 },
});
}
}Configuration Options
SearchServiceModule.forRoot(options)
| Option | Type | Required | Default | Description |
| ---------------- | -------- | -------- | ------------------------ | ---------------------------------------- |
| mongoUri | string | ✅ | — | MongoDB connection URI |
| dbName | string | ❌ | URI default | Database name |
| cohereApiKey | string | ❌ | — | Cohere API key (for vector search) |
| embeddingModel | string | ❌ | embed-english-v3.0 | Cohere embedding model |
| isGlobalConfig | boolean| ❌ | true | Register ConfigModule globally |
SearchServiceModule.forRootFromEnv()
Reads config from environment variables — no options needed:
SearchServiceModule.forRootFromEnv()| Env Variable | Required | Description |
| ------------------------ | -------- | --------------------------- |
| MONGO_URI | ✅ | MongoDB connection URI |
| DB_NAME | ❌ | Database name |
| COHERE_API_KEY | ❌ | Cohere API key |
| EMBEDDING_MODEL | ❌ | Cohere model name |
| EMBEDDINGS_CACHE_MAX_SIZE | ❌ | Max cache entries (default: 10000) |
Registering a Collection
Before you can search, register your MongoDB collection so the service knows which fields are searchable:
// Register via CollectionsService (programmatically)
const result = await collectionsService.registerCollection({
collectionName: 'products',
searchableFields: {
text: ['name', 'description', 'brand'], // required — at least one field
autocomplete: ['name', 'brand'], // optional
filters: ['category', 'price', 'inStock'], // optional
sortable: ['price', 'rating', 'createdAt'], // optional
},
searchDefaults: {
fuzzyMaxEdits: 2, // 0, 1, or 2 — typo tolerance
limit: 20, // default results per page
minScore: 0.5, // minimum relevance threshold
},
});
// The response includes Atlas Search index templates
// Create these indexes in MongoDB Atlas Dashboard, then:
await collectionsService.markIndexesCreated('products');Or use the REST API (auto-registered at /api/v1/collections):
# Register collection
curl -X POST http://localhost:3000/api/v1/collections \
-H "Content-Type: application/json" \
-d '{
"collectionName": "products",
"searchableFields": {
"text": ["name", "description"],
"autocomplete": ["name"]
}
}'
# Get index templates to create in Atlas
curl http://localhost:3000/api/v1/collections/products/templates
# Mark indexes as created
curl -X POST http://localhost:3000/api/v1/collections/products/indexes-createdSearch Types
Text Search
Basic full-text search using MongoDB $text operator. Requires a text index on the collection.
const results = await search.search({
collectionName: 'products',
query: 'wireless headphones',
searchType: SearchType.TEXT,
});Fuzzy Search
Typo-tolerant search via MongoDB Atlas Search. Handles misspellings gracefully.
const results = await search.search({
collectionName: 'products',
query: 'wireles headfones', // typos are OK!
searchType: SearchType.FUZZY,
fuzzyMaxEdits: 2,
});Autocomplete
Prefix-based suggestions as the user types. Returns max 10 results.
const results = await search.search({
collectionName: 'products',
query: 'iph', // matches "iPhone", "iPhone case", etc.
searchType: SearchType.AUTOCOMPLETE,
pagination: { limit: 5 },
});Hybrid Search
Combines fuzzy search with text search fallback. Tries fuzzy first, falls back to text if Atlas Search is unavailable.
const results = await search.search({
collectionName: 'products',
query: 'laptop',
searchType: SearchType.HYBRID,
});Vector / Semantic Search
AI-powered meaning-based search. Finds results by meaning, not just keyword matching.
Prerequisites:
- Set
cohereApiKeyin module config - Generate embeddings for your documents (see below)
- Create a vector search index in MongoDB Atlas
// Search by meaning
const results = await search.search({
collectionName: 'products',
query: 'something to listen to music wirelessly',
searchType: SearchType.VECTOR,
});
// Generate embeddings for all documents in a collection
// POST /api/v1/embeddings/generate-for-collection
// { "collectionName": "products", "nameField": "name", "descriptionField": "description" }Available Services
Everything is importable from the top-level package:
import {
// Module
SearchServiceModule,
// Search
SearchOrchestratorService, // Main search orchestrator
StrategyFactoryService, // Get search strategy by type
SearchType, // Enum: TEXT, FUZZY, AUTOCOMPLETE, VECTOR, HYBRID
SearchRequestDto, // Search request structure
SearchResponseDto, // Search response structure
// Collections
CollectionsService, // Register/manage collections
RegisterCollectionDto, // Registration request structure
UpdateCollectionDto, // Update request structure
// Database
DatabaseService, // Connection management & health checks
BaseRepository, // Generic CRUD for any collection
SearchRepository, // Search-specific operations
AggregationRepository, // Complex aggregations
// Embeddings
EmbeddingsService, // Generate AI embeddings
EmbeddingsCache, // Embedding cache management
CohereProvider, // Cohere AI provider
// Strategies (for advanced use)
TextSearchStrategy,
FuzzySearchStrategy,
AutocompleteStrategy,
HybridSearchStrategy,
VectorSearchStrategy,
} from '@rohanadnan1/search-service';REST API Endpoints
When you import SearchServiceModule, these endpoints are automatically available:
Search
| Method | Endpoint | Description |
| ------ | ----------------------------- | ------------------------ |
| POST | /api/v1/search | Execute a search |
| POST | /api/v1/search/autocomplete | Autocomplete suggestions |
Collections
| Method | Endpoint | Description |
| ------ | --------------------------------------------- | -------------------------- |
| POST | /api/v1/collections | Register a collection |
| GET | /api/v1/collections | List all collections |
| GET | /api/v1/collections/:configId | Get collection config |
| PATCH | /api/v1/collections/:configId | Update collection config |
| DELETE | /api/v1/collections/:configId | Delete collection config |
| POST | /api/v1/collections/:configId/indexes-created | Mark indexes as created |
| GET | /api/v1/collections/:configId/templates | Get Atlas index templates |
Embeddings
| Method | Endpoint | Description |
| ------ | ------------------------------------------------- | -------------------------- |
| POST | /api/v1/embeddings/generate | Generate single embedding |
| POST | /api/v1/embeddings/batch | Generate batch embeddings |
| POST | /api/v1/embeddings/generate-for-collection | Embed all docs in collection |
| GET | /api/v1/embeddings/info | Provider info & stats |
| GET | /api/v1/embeddings/health | Check Cohere availability |
| DELETE | /api/v1/embeddings/cache | Clear embedding cache |
Sub-Module Imports
You can also import individual modules if you only need specific functionality:
// Only database operations
import { DatabaseModule, DatabaseService, BaseRepository } from '@rohanadnan1/search-service/database';
// Only collection management
import { CollectionsModule, CollectionsService } from '@rohanadnan1/search-service/collections';
// Only embeddings
import { EmbeddingsModule, EmbeddingsService } from '@rohanadnan1/search-service/embeddings';
// Only search
import { SearchModule, SearchOrchestratorService, SearchType } from '@rohanadnan1/search-service/search';Full Integration Example
Here's a complete example of integrating into an existing NestJS e-commerce backend:
// app.module.ts
import { Module } from '@nestjs/common';
import { SearchServiceModule } from '@rohanadnan1/search-service';
import { ProductsModule } from './products/products.module';
@Module({
imports: [
SearchServiceModule.forRoot({
mongoUri: process.env.MONGO_URI!,
cohereApiKey: process.env.COHERE_API_KEY, // optional
}),
ProductsModule,
],
})
export class AppModule {}// products/products.service.ts
import { Injectable } from '@nestjs/common';
import {
SearchOrchestratorService,
CollectionsService,
SearchType,
BaseRepository,
} from '@rohanadnan1/search-service';
@Injectable()
export class ProductsService {
constructor(
private readonly search: SearchOrchestratorService,
private readonly collections: CollectionsService,
private readonly repo: BaseRepository,
) {}
/**
* One-time setup: register your collection
* Call this once during app initialization
*/
async setupSearch() {
try {
await this.collections.registerCollection({
collectionName: 'products',
searchableFields: {
text: ['name', 'description', 'brand'],
autocomplete: ['name'],
filters: ['category', 'price'],
sortable: ['price', 'rating'],
},
});
} catch (error) {
// Collection already registered — that's fine
}
}
async searchProducts(query: string, page = 1) {
return this.search.search({
collectionName: 'products',
query,
searchType: SearchType.FUZZY,
pagination: { limit: 20, page },
filterOptions: {
filters: { category: 'electronics' },
sort: { price: 1 },
},
});
}
async getSuggestions(query: string) {
return this.search.search({
collectionName: 'products',
query,
searchType: SearchType.AUTOCOMPLETE,
pagination: { limit: 5 },
});
}
async findProduct(id: string) {
return this.repo.findById('products', id);
}
}// products/products.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { ProductsService } from './products.service';
@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
@Get('search')
async search(
@Query('q') query: string,
@Query('page') page?: string,
) {
return this.productsService.searchProducts(query, Number(page) || 1);
}
@Get('suggest')
async suggest(@Query('q') query: string) {
return this.productsService.getSuggestions(query);
}
}MongoDB Atlas Setup
For fuzzy search and autocomplete, you need MongoDB Atlas Search indexes:
- Go to your MongoDB Atlas Dashboard
- Navigate to Database → Search
- Click Create Search Index
- Use the templates from
GET /api/v1/collections/:configId/templates - After creating, call
POST /api/v1/collections/:configId/indexes-created
For vector search, also create a Vector Search index:
{
"type": "vectorSearch",
"fields": [
{
"type": "vector",
"path": "embedding",
"numDimensions": 1024,
"similarity": "cosine"
}
]
}License
MIT © Rohan Adnan
