@faiss-node/native 0.1.2
High-performance Node.js native bindings for Facebook FAISS - the fastest vector similarity search library. Supports FLAT_L2, IVF_FLAT, and HNSW index types with async operations, persistence, and batch search.
Loading...
Searching...
No Matches
@faiss-node/native

npm version Node.js Version License: MIT Documentation

High-performance Node.js native bindings for Facebook FAISS - the industry-standard vector similarity search library. Built for semantic search, RAG applications, and vector databases.

Features

  • 🚀 Async Operations - Non-blocking Promise-based API that never blocks the event loop
  • 🔒 Thread-Safe - Mutex-protected concurrent operations for production workloads
  • 📦 Multiple Index Types - FLAT_L2, IVF_FLAT, and HNSW with optimized defaults
  • 💾 Persistence - Save/load indexes to disk or serialize to buffers
  • High Performance - Direct C++ bindings with zero-copy data transfer
  • 📚 TypeScript Support - Full type definitions included

Installation

Quick Install

npm install @faiss-node/native

Prerequisites

macOS:

brew install cmake libomp openblas faiss

Linux (Ubuntu/Debian):

sudo apt-get update
sudo apt-get install -y cmake libopenblas-dev libomp-dev
# Build FAISS from source (see below)

Windows: Windows native builds require FAISS to be installed, which can be complex. We recommend using one of these approaches:

  1. WSL2 (Recommended): Use Windows Subsystem for Linux 2 - see WINDOWS.md
    • After installing WSL2, follow the Linux instructions above
    • Works seamlessly from Windows Terminal and VS Code
  2. VS Code Dev Container: Use the included .devcontainer configuration - see WINDOWS.md
    • Best for teams and consistent development environments
    • No manual setup required - just "Reopen in Container"
  3. Docker Desktop: Run the project in a container - see WINDOWS.md
    • Full control over the container environment
    • Works with any IDE or editor

Note for npm users: The npm package (@faiss-node/native) works on Windows when installed in WSL2, Dev Containers, or Docker. For Windows native development, see WINDOWS.md for detailed setup instructions.

Building FAISS from Source (Linux/WSL2):

git clone https://github.com/facebookresearch/faiss.git
cd faiss
cmake -B build -DFAISS_ENABLE_GPU=OFF -DFAISS_ENABLE_PYTHON=OFF
cmake --build build -j$(nproc)
sudo cmake --install build

Build Native Module

After installing prerequisites:

npm run build

Quick Start

const { FaissIndex } = require('@faiss-node/native');
// Create an index
const index = new FaissIndex({ type: 'FLAT_L2', dims: 128 });
// Add vectors (single or batch)
const vectors = new Float32Array([
1.0, 0.0, 0.0, 0.0, // Vector 1
0.0, 1.0, 0.0, 0.0, // Vector 2
0.0, 0.0, 1.0, 0.0 // Vector 3
]);
await index.add(vectors);
// Search for nearest neighbors
const query = new Float32Array([1.0, 0.0, 0.0, 0.0]);
const results = await index.search(query, 2);
console.log('Labels:', results.labels); // Int32Array: [0, 1]
console.log('Distances:', results.distances); // Float32Array: [0, 2]
// Cleanup
index.dispose();

API Reference

Constructor

Create a new FAISS index with the specified configuration.

const index = new FaissIndex(config);

Parameters:

  • config.type (string, required): Index type - ‘'FLAT_L2’,'IVF_FLAT', or'HNSW' -config.dims<tt>(number, required): Vector dimensions (must be positive integer) -config.nlist<tt>(number, optional): Number of clusters for IVF_FLAT (default: 100) -config.nprobe<tt>(number, optional): Clusters to search for IVF_FLAT (default: 10) -config.M<tt>(number, optional): Connections per node for HNSW (default: 16) -config.efConstruction<tt>(number, optional): HNSW construction parameter (default: 200) -config.efSearch` (number, optional): HNSW search parameter (default: 50)

Examples:

// FLAT_L2 - Exact search (best for small datasets < 10k vectors)
const flatIndex = new FaissIndex({ type: 'FLAT_L2', dims: 128 });
// IVF_FLAT - Fast approximate search (best for 10k - 1M vectors)
const ivfIndex = new FaissIndex({
type: 'IVF_FLAT',
dims: 768,
nlist: 100, // Number of clusters
nprobe: 10 // Clusters to search (higher = more accurate, slower)
});
await ivfIndex.train(trainingVectors); // Must train before adding vectors!
// HNSW - State-of-the-art approximate search (best for large datasets)
const hnswIndex = new FaissIndex({
type: 'HNSW',
dims: 1536,
M: 16, // Connections per node (higher = more accurate, slower)
efConstruction: 200, // Construction parameter
efSearch: 50 // Search parameter (higher = more accurate, slower)
});

Methods

<tt>add(vectors: Float32Array): Promise<void></tt>

Add vectors to the index. Can add a single vector or a batch of vectors.

// Single vector
await index.add(new Float32Array([1, 2, 3, 4]));
// Batch of vectors (4 vectors of 4 dimensions each)
await index.add(new Float32Array([
1, 0, 0, 0, // Vector 1
0, 1, 0, 0, // Vector 2
0, 0, 1, 0, // Vector 3
0, 0, 0, 1 // Vector 4
]));

Note: For IVF_FLAT indexes, you must call train() before adding vectors.

<tt>search(query: Float32Array, k: number): Promise<SearchResults></tt>

Search for k nearest neighbors.

const query = new Float32Array([1, 0, 0, 0]);
const results = await index.search(query, 5);
// results.distances: Float32Array of L2 distances
// results.labels: Int32Array of vector indices

Returns:

  • distances (Float32Array): L2 distances to nearest neighbors
  • labels (Int32Array): Indices of nearest neighbors

<tt>searchBatch(queries: Float32Array, k: number): Promise<SearchResults></tt>

Perform batch search for multiple queries efficiently.

// 3 queries of 4 dimensions each
const queries = new Float32Array([
1, 0, 0, 0, // Query 1
0, 1, 0, 0, // Query 2
0, 0, 1, 0 // Query 3
]);
const results = await index.searchBatch(queries, 5);
// results.distances: Float32Array of shape [3 * 5]
// results.labels: Int32Array of shape [3 * 5]

<tt>train(vectors: Float32Array): Promise<void></tt>

Train an IVF_FLAT index. Required before adding vectors.

// Training vectors (typically 10k-100k vectors)
const trainingVectors = new Float32Array(/* ... */);
await ivfIndex.train(trainingVectors);
await ivfIndex.add(dataVectors); // Now you can add vectors

<tt>setNprobe(nprobe: number): Promise<void></tt>

Set the number of clusters to search for IVF_FLAT indexes.

await ivfIndex.setNprobe(20); // Search more clusters (more accurate, slower)

<tt>getStats(): IndexStats</tt>

Get index statistics.

const stats = index.getStats();
// {
// ntotal: number, // Total vectors in index
// dims: number, // Vector dimensions
// isTrained: boolean, // Whether index is trained (IVF only)
// type: string // Index type
// }

<tt>save(filename: string): Promise<void></tt>

Save index to disk.

await index.save('./my-index.faiss');

<tt>static load(filename: string): Promise<FaissIndex></tt>

Load index from disk.

const index = await FaissIndex.load('./my-index.faiss');

<tt>toBuffer(): Promise<Buffer></tt>

Serialize index to a Node.js Buffer (useful for databases, network transfer, etc.).

const buffer = await index.toBuffer();
// Store in database, send over network, etc.

<tt>static fromBuffer(buffer: Buffer): Promise<FaissIndex></tt>

Deserialize index from Buffer.

const index = await FaissIndex.fromBuffer(buffer);

<tt>mergeFrom(otherIndex: FaissIndex): Promise<void></tt>

Merge vectors from another index into this index.

const index1 = new FaissIndex({ type: 'FLAT_L2', dims: 128 });
const index2 = new FaissIndex({ type: 'FLAT_L2', dims: 128 });
await index1.add(vectors1);
await index2.add(vectors2);
await index1.mergeFrom(index2); // index1 now contains vectors from both
// Note: index2 is now empty (FAISS behavior)

<tt>dispose(): void</tt>

Explicitly dispose of the index and free resources. Optional - automatic on garbage collection.

index.dispose();
// Index is now unusable - all operations will throw errors

Choosing the Right Index Type

FLAT_L2 (IndexFlatL2)

  • Best for: Small datasets (< 10k vectors), exact search required
  • Speed: O(n) per query - linear scan
  • Accuracy: 100% recall (exact results)
  • Memory: 4 × dims × n bytes
  • Use case: Prototyping, small production datasets, when accuracy is critical

IVF_FLAT (IndexIVFFlat)

  • Best for: Medium datasets (10k - 1M vectors), can tolerate ~90-95% recall
  • Speed: O(nprobe × n/nlist) per query - much faster than FLAT
  • Accuracy: ~90-95% recall (configurable via nprobe)
  • Memory: Similar to FLAT + cluster overhead
  • Requires: Training on sample data before use
  • Use case: Production systems with medium-sized datasets

HNSW (IndexHNSW)

  • Best for: Large datasets (> 100k vectors), best speed/accuracy tradeoff
  • Speed: O(log n) per query - logarithmic search
  • Accuracy: ~95-99% recall (configurable via efSearch)
  • Memory: ~1.5-2× more than FLAT
  • No training required
  • Use case: Large-scale production systems, best overall performance

Examples

Basic Semantic Search

const { FaissIndex } = require('@faiss-node/native');
// Create index for 768-dimensional embeddings (e.g., OpenAI)
const index = new FaissIndex({ type: 'HNSW', dims: 768 });
// Add document embeddings
const documents = [
{ id: 0, text: "JavaScript is a programming language" },
{ id: 1, text: "Python is great for data science" },
{ id: 2, text: "Node.js runs JavaScript on the server" }
];
const embeddings = new Float32Array(/* ... your embeddings ... */);
await index.add(embeddings);
// Search for similar documents
const queryEmbedding = new Float32Array(/* ... query embedding ... */);
const results = await index.search(queryEmbedding, 3);
console.log('Most similar documents:', results.labels);

RAG Pipeline

const { FaissIndex } = require('@faiss-node/native');
class RAGSystem {
constructor() {
this.index = new FaissIndex({ type: 'HNSW', dims: 1536 });
this.documents = [];
}
async addDocuments(docs, embeddings) {
this.documents.push(...docs);
await this.index.add(embeddings);
}
async search(queryEmbedding, k = 5) {
const results = await this.index.search(queryEmbedding, k);
return results.labels.map(idx => this.documents[idx]);
}
async save(path) {
await this.index.save(path);
// Also save documents mapping
}
}

Persistence

const { FaissIndex } = require('@faiss-node/native');
// Save to disk
const index = new FaissIndex({ type: 'HNSW', dims: 128 });
await index.add(vectors);
await index.save('./index.faiss');
// Load from disk
const loadedIndex = await FaissIndex.load('./index.faiss');
// Or serialize to buffer (for databases)
const buffer = await index.toBuffer();
// Store in MongoDB, Redis, etc.
const restoredIndex = await FaissIndex.fromBuffer(buffer);

Migration Guide

From Python FAISS

If you're familiar with Python FAISS, migrating to @faiss-node/native is straightforward. Here are common patterns translated from Python to Node.js:

Basic Index Creation and Search

Python FAISS:

import faiss
import numpy as np
# Create index
d = 128 # dimensions
index = faiss.IndexFlatL2(d)
# Add vectors (numpy array)
vectors = np.random.random((1000, d)).astype('float32')
index.add(vectors)
# Search
query = np.random.random((1, d)).astype('float32')
k = 10
distances, labels = index.search(query, k)
print(distances) # [[0.1, 0.2, ...]]
print(labels) # [[0, 1, ...]]

Node.js (@faiss-node/native):

const { FaissIndex } = require('@faiss-node/native');
// Create index
const d = 128; // dimensions
const index = new FaissIndex({ type: 'FLAT_L2', dims: d });
// Add vectors (Float32Array)
const vectors = new Float32Array(1000 * d);
for (let i = 0; i < vectors.length; i++) {
vectors[i] = Math.random();
}
await index.add(vectors);
// Search
const query = new Float32Array(d);
for (let i = 0; i < d; i++) {
query[i] = Math.random();
}
const k = 10;
const results = await index.search(query, k);
console.log(results.distances); // Float32Array: [0.1, 0.2, ...]
console.log(results.labels); // Int32Array: [0, 1, ...]

IVF_FLAT Index (with Training)

Python FAISS:

import faiss
d = 768
nlist = 100
index = faiss.IndexIVFFlat(faiss.IndexFlatL2(d), d, nlist)
# Train on sample data
training_vectors = np.random.random((10000, d)).astype('float32')
index.train(training_vectors)
# Add vectors
vectors = np.random.random((100000, d)).astype('float32')
index.add(vectors)
# Set nprobe for search
index.nprobe = 10
distances, labels = index.search(query, k)

Node.js (@faiss-node/native):

const { FaissIndex } = require('@faiss-node/native');
const d = 768;
const nlist = 100;
const index = new FaissIndex({
type: 'IVF_FLAT',
dims: d,
nlist: nlist
});
// Train on sample data
const trainingVectors = new Float32Array(10000 * d);
for (let i = 0; i < trainingVectors.length; i++) {
trainingVectors[i] = Math.random();
}
await index.train(trainingVectors);
// Add vectors
const vectors = new Float32Array(100000 * d);
for (let i = 0; i < vectors.length; i++) {
vectors[i] = Math.random();
}
await index.add(vectors);
// Set nprobe for search
index.setNprobe(10);
const results = await index.search(query, k);

HNSW Index

Python FAISS:

import faiss
d = 1536
M = 16
index = faiss.IndexHNSWFlat(d, M)
# Add vectors (no training needed)
vectors = np.random.random((1000000, d)).astype('float32')
index.add(vectors)
# Search with ef parameter
index.hnsw.efSearch = 50
distances, labels = index.search(query, k)

Node.js (@faiss-node/native):

const { FaissIndex } = require('@faiss-node/native');
const d = 1536;
const index = new FaissIndex({
type: 'HNSW',
dims: d,
M: 16, // Connections per node
efSearch: 50 // Search parameter (equivalent to index.hnsw.efSearch)
});
// Add vectors (no training needed)
const vectors = new Float32Array(1000000 * d);
for (let i = 0; i < vectors.length; i++) {
vectors[i] = Math.random();
}
await index.add(vectors);
// Search (efSearch already set in constructor)
const results = await index.search(query, k);

Save and Load Index

Python FAISS:

# Save to disk
faiss.write_index(index, "index.faiss")
# Load from disk
loaded_index = faiss.read_index("index.faiss")

Node.js (@faiss-node/native):

// Save to disk (async)
await index.save("index.faiss");
// Load from disk (static method, async)
const loadedIndex = await FaissIndex.load("index.faiss");

Batch Search (Multiple Queries)

Python FAISS:

# Multiple queries (nq queries)
queries = np.random.random((100, d)).astype('float32')
distances, labels = index.search(queries, k)
# distances shape: (100, k)
# labels shape: (100, k)

Node.js (@faiss-node/native):

// Multiple queries (nq queries)
const queries = new Float32Array(100 * d);
for (let i = 0; i < queries.length; i++) {
queries[i] = Math.random();
}
const results = await index.searchBatch(queries, k);
// results.distances: Float32Array of shape [100 * k]
// results.labels: Int32Array of shape [100 * k]
// results.nq: 100
// results.k: k

Key Differences

Feature Python FAISS Node.js (@faiss-node/native)
Index Creation faiss.IndexFlatL2(d) ‘new FaissIndex({ type: 'FLAT_L2’, dims: d })\ilinebr </td> </tr> <tr class="markdownTableRowEven"> <td class="markdownTableBodyNone"> **Add Vectors** \ilinebr </td> <td class="markdownTableBodyNone">index.add(vectors)(synchronous) \ilinebr </td> <td class="markdownTableBodyNone">await index.add(vectors)(async) \ilinebr </td> </tr> <tr class="markdownTableRowOdd"> <td class="markdownTableBodyNone"> **Search** \ilinebr </td> <td class="markdownTableBodyNone">index.search(queries, k)(synchronous) \ilinebr </td> <td class="markdownTableBodyNone">await index.search(query, k)(async) \ilinebr </td> </tr> <tr class="markdownTableRowEven"> <td class="markdownTableBodyNone"> **Batch Search** \ilinebr </td> <td class="markdownTableBodyNone">index.search(queries, k)(same method) \ilinebr </td> <td class="markdownTableBodyNone">await index.searchBatch(queries, k)(separate method) \ilinebr </td> </tr> <tr class="markdownTableRowOdd"> <td class="markdownTableBodyNone"> **Training** \ilinebr </td> <td class="markdownTableBodyNone">index.train(vectors)(synchronous) \ilinebr </td> <td class="markdownTableBodyNone">await index.train(vectors)(async) \ilinebr </td> </tr> <tr class="markdownTableRowEven"> <td class="markdownTableBodyNone"> **Save/Load** \ilinebr </td> <td class="markdownTableBodyNone">faiss.write_index()/faiss.read_index()(synchronous) \ilinebr </td> <td class="markdownTableBodyNone">await index.save()/await FaissIndex.load()(async) \ilinebr </td> </tr> <tr class="markdownTableRowOdd"> <td class="markdownTableBodyNone"> **nprobe (IVF)** \ilinebr </td> <td class="markdownTableBodyNone">index.nprobe = 10\ilinebr </td> <td class="markdownTableBodyNone">index.setNprobe(10)\ilinebr </td> </tr> <tr class="markdownTableRowEven"> <td class="markdownTableBodyNone"> **efSearch (HNSW)** \ilinebr </td> <td class="markdownTableBodyNone">index.hnsw.efSearch = 50\ilinebr </td> <td class="markdownTableBodyNone"> Set in constructor or useefSearchconfig \ilinebr </td> </tr> <tr class="markdownTableRowOdd"> <td class="markdownTableBodyNone"> **Vector Format** \ilinebr </td> <td class="markdownTableBodyNone">numpy.ndarray<tt>(float32) \ilinebr </td> <td class="markdownTableBodyNone">Float32Array\ilinebr </td> </tr> <tr class="markdownTableRowEven"> <td class="markdownTableBodyNone"> **Results Format** \ilinebr </td> <td class="markdownTableBodyNone"> Tuple(distances, labels)as numpy arrays \ilinebr </td> <td class="markdownTableBodyNone"> Object{ distances: Float32Array, labels: Int32Array }`

Common Patterns

Pattern 1: Converting numpy arrays to Float32Array

If you have Python code that generates embeddings and want to use them in Node.js:

# Python: Save embeddings
import numpy as np
embeddings = model.encode(texts) # numpy array, shape (n, d)
np.save('embeddings.npy', embeddings)
// Node.js: Load embeddings
const fs = require('fs');
// Assuming you converted .npy to binary format
const embeddingsBuffer = fs.readFileSync('embeddings.bin');
const embeddings = new Float32Array(embeddingsBuffer.buffer);
await index.add(embeddings);

Pattern 2: Chunked Add Operations

Python FAISS:

batch_size = 10000
for i in range(0, len(vectors), batch_size):
batch = vectors[i:i+batch_size]
index.add(batch)

Node.js (@faiss-node/native):

const batchSize = 10000;
for (let i = 0; i < vectors.length; i += batchSize * dims) {
const batch = vectors.slice(i, i + batchSize * dims);
await index.add(batch);
}

Pattern 3: Memory Management

Python FAISS:

# Index is automatically garbage collected
# But you can delete explicitly:
del index

Node.js (@faiss-node/native):

// Explicitly dispose to free native memory
index.dispose();
// Or let garbage collector handle it
// (but explicit dispose is recommended for large indexes)

Migration Checklist

  • [ ] Replace import faiss with ‘require(’@faiss-node/native')
  • [ ] Convertnumpy.ndarraytoFloat32Array
  • [ ] Addawaitto all async operations (add, search, train, save, load)
  • [ ] Replaceindex.search(queries, k)for batch withindex.searchBatch(queries, k)
  • [ ] Use constructor config object instead of direct index instantiation
  • [ ] Replaceindex.nprobe = Xwithindex.setNprobe(X)for IVF
  • [ ] SetefSearchin constructor config for HNSW instead ofindex.hnsw.efSearch
  • [ ] Handle results as{ distances, labels }object instead of tuple
  • [ ] Addindex.dispose()` when done with large indexes (optional but recommended)

Performance Tips

  1. Use HNSW for large datasets - Best overall performance
  2. Batch operations - Use searchBatch() for multiple queries
  3. Train IVF properly - Use 10k-100k training vectors
  4. Tune parameters - Increase nprobe (IVF) or efSearch (HNSW) for accuracy
  5. Reuse indexes - Save/load instead of recreating

For detailed benchmarks and performance comparisons, see examples/benchmark.js.

Thread Safety

All operations are thread-safe and can be called concurrently:

// Safe to call from multiple async operations
await Promise.all([
index.add(vectors1),
index.add(vectors2),
index.search(query1),
index.search(query2)
]);

The implementation uses mutex locks to ensure FAISS operations are serialized safely.

Error Handling

All methods throw JavaScript errors (not C++ exceptions):

try {
await index.add(vectors);
} catch (error) {
if (error.message.includes('disposed')) {
console.error('Index was disposed');
} else if (error.message.includes('dimensions')) {
console.error('Vector dimensions mismatch');
}
}

TypeScript Support

Full TypeScript definitions are included:

import { FaissIndex, FaissIndexConfig, SearchResults } from '@faiss-node/native';
const config: FaissIndexConfig = {
type: 'HNSW',
dims: 768
};
const index = new FaissIndex(config);
const results: SearchResults = await index.search(query, 10);

Updating

To update to the latest version:

npm update @faiss-node/native

Or install a specific version:

npm install @faiss-node/native@0.1.2

Development

Building from Source

macOS/Linux:

# Clone repository
git clone https://github.com/anupammaurya6767/faiss-node-native.git
cd faiss-node-native
# Install dependencies
npm install
# Build native module
npm run build
# Run tests
npm test

Windows: Windows users should use WSL2 or VS Code Dev Container. See WINDOWS.md for detailed setup instructions.

VS Code Dev Container (All Platforms):

# Open in VS Code and select "Reopen in Container"
# Or from command palette: "Dev Containers: Reopen in Container"
# First build will take 5-10 minutes (compiles FAISS)

Running Tests

npm test # All tests
npm run test:unit # Unit tests only
npm run test:integration # Integration tests only
npm run test:ci # CI tests (faster, no manual tests)

Generating Documentation

npm run docs # Generate Doxygen documentation
npm run docs:serve # Serve docs locally at http://localhost:8000

Documentation

  • API Documentation: GitHub Pages
  • Examples: See examples/ directory
  • Contributing: See CONTRIBUTING.md

Troubleshooting

Build Errors

macOS: "library not found"

# Ensure FAISS is installed
brew install faiss
# Check installation
ls /usr/local/lib/libfaiss*

Linux: "faiss/Index.h: No such file or directory"

# Build and install FAISS from source (see Prerequisites)
# Ensure CMAKE_INSTALL_PREFIX=/usr/local
# Run ldconfig after installation
sudo ldconfig

Windows: Build errors or missing dependencies

  • Use WSL2 instead of native Windows - see WINDOWS.md
  • Or use VS Code Dev Container - see WINDOWS.md
  • Ensure Docker Desktop uses WSL2 backend if using containers

Runtime Errors

**"Index has been disposed"**

  • You called dispose() or the index was garbage collected
  • Create a new index or don't dispose until done

**"Vector dimensions don't match"**

  • Check that your vectors are the correct size
  • For batch operations: vectors.length % dims === 0

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

MIT License - see LICENSE file for details.

Author

Anupam Maurya

Acknowledgments

  • Built on Facebook FAISS - the amazing vector similarity search library
  • Inspired by the need for high-performance vector search in Node.js
  • Thanks to the open-source community for feedback and contributions

Made with ❤️ for the Node.js community