DEV Community

Cover image for MongoDB-Style Filtering in TypeScript with @mcabreradev/filter
Miguelángel Cabrera
Miguelángel Cabrera

Posted on • Originally published at mcabreradev-filter.vercel.app

MongoDB-Style Filtering in TypeScript with @mcabreradev/filter

TL;DR

Use MongoDB's familiar query syntax ($gt, $in, $regex) to filter JavaScript arrays with full TypeScript type safety:

import { filter } from '@mcabreradev/filter';

// Instead of this ❌
products.filter(p => p.price > 100 && p.price < 500 && p.inStock)

// Write this ✅
filter(products, {
  price: { $gte: 100, $lte: 500 },
  inStock: true
})
Enter fullscreen mode Exit fullscreen mode

Benefits: 18+ operators, zero dependencies, lazy evaluation, framework integrations (React/Vue/Svelte), 100% type-safe. Try it live →


MongoDB-Style Filtering in TypeScript with @mcabreradev/filter

If you've ever worked with MongoDB, you know how powerful and intuitive its query operators are. What if you could use that same familiar syntax for filtering JavaScript arrays and objects? That's exactly what @mcabreradev/filter enables!

In this post, I'll show you how to leverage MongoDB-style operators for type-safe filtering in TypeScript.

🎯 Why MongoDB-Style Filtering?

MongoDB's query language is:

  • Intuitive: Familiar operators like $gt, $in, $regex
  • Powerful: Complex nested queries with logical operators
  • Expressive: Clear intent with declarative syntax

Now you can use this same approach in your TypeScript applications, with full type safety!

🚀 Quick Start

First, install the package:

npm install @mcabreradev/filter
# or
pnpm add @mcabreradev/filter
# or
yarn add @mcabreradev/filter
Enter fullscreen mode Exit fullscreen mode

📊 Basic MongoDB-Style Queries

Comparison Operators

Just like MongoDB, you can use familiar comparison operators:

import { filter } from '@mcabreradev/filter';

interface Product {
  name: string;
  price: number;
  stock: number;
  rating: number;
}

const products: Product[] = [
  { name: 'Laptop', price: 999, stock: 5, rating: 4.5 },
  { name: 'Mouse', price: 25, stock: 50, rating: 4.2 },
  { name: 'Keyboard', price: 75, stock: 30, rating: 4.7 },
  { name: 'Monitor', price: 299, stock: 0, rating: 4.3 },
];

// Greater than operator ($gt)
const expensive = filter(products, {
  price: { $gt: 100 }
});
// Result: [Laptop, Monitor]

// Less than or equal ($lte)
const affordable = filter(products, {
  price: { $lte: 100 }
});
// Result: [Mouse, Keyboard]

// Not equal ($ne)
const inStock = filter(products, {
  stock: { $ne: 0 }
});
// Result: [Laptop, Mouse, Keyboard]

// Between values (combining operators)
const midRange = filter(products, {
  price: { $gte: 50, $lte: 300 }
});
// Result: [Keyboard, Monitor]
Enter fullscreen mode Exit fullscreen mode

Array Operators

Work with arrays just like in MongoDB:

interface User {
  name: string;
  age: number;
  tags: string[];
  favoriteColors: string[];
}

const users: User[] = [
  { name: 'Alice', age: 25, tags: ['admin', 'developer'], favoriteColors: ['blue', 'green'] },
  { name: 'Bob', age: 30, tags: ['developer'], favoriteColors: ['red'] },
  { name: 'Charlie', age: 35, tags: ['designer', 'developer'], favoriteColors: ['blue', 'yellow'] },
];

// $in operator - value in array
const youngUsers = filter(users, {
  age: { $in: [25, 30] }
});
// Result: [Alice, Bob]

// $contains - array contains value
const developers = filter(users, {
  tags: { $contains: 'developer' }
});
// Result: [Alice, Bob, Charlie]

// $all - array contains all values
const adminDevelopers = filter(users, {
  tags: { $all: ['admin', 'developer'] }
});
// Result: [Alice]

// $size - array length
const multiColor = filter(users, {
  favoriteColors: { $size: 2 }
});
// Result: [Alice, Charlie]
Enter fullscreen mode Exit fullscreen mode

String Operators

MongoDB-style string matching with full regex support:

interface Article {
  title: string;
  content: string;
  author: string;
}

const articles: Article[] = [
  { title: 'Getting Started with TypeScript', content: '...', author: 'John Doe' },
  { title: 'Advanced TypeScript Patterns', content: '...', author: 'Jane Smith' },
  { title: 'JavaScript Best Practices', content: '...', author: 'John Adams' },
];

// $regex operator
const tsArticles = filter(articles, {
  title: { $regex: /typescript/i }
});
// Result: First two articles

// $startsWith
const gettingStarted = filter(articles, {
  title: { $startsWith: 'Getting' }
});

// $endsWith
const patterns = filter(articles, {
  title: { $endsWith: 'Patterns' }
});

// $contains (case-insensitive by default)
const johnAuthors = filter(articles, {
  author: { $contains: 'john' }
});
// Result: [John Doe, John Adams]
Enter fullscreen mode Exit fullscreen mode

🎭 Logical Operators

Combine conditions just like MongoDB's $and, $or, $not:

interface Task {
  title: string;
  priority: 'low' | 'medium' | 'high';
  completed: boolean;
  assignee: string;
  dueDate: Date;
}

const tasks: Task[] = [
  { title: 'Fix bug #123', priority: 'high', completed: false, assignee: 'Alice', dueDate: new Date('2025-10-30') },
  { title: 'Write docs', priority: 'medium', completed: true, assignee: 'Bob', dueDate: new Date('2025-10-29') },
  { title: 'Code review', priority: 'low', completed: false, assignee: 'Alice', dueDate: new Date('2025-10-28') },
];

// $and - all conditions must match
const urgentTasks = filter(tasks, {
  $and: [
    { priority: 'high' },
    { completed: false }
  ]
});

// $or - at least one condition must match
const aliceOrHighPriority = filter(tasks, {
  $or: [
    { assignee: 'Alice' },
    { priority: 'high' }
  ]
});

// $not - negation
const notCompleted = filter(tasks, {
  $not: { completed: true }
});

// Complex nested queries
const criticalTasks = filter(tasks, {
  $and: [
    {
      $or: [
        { priority: 'high' },
        { dueDate: { $lte: new Date('2025-10-29') } }
      ]
    },
    { completed: false }
  ]
});
Enter fullscreen mode Exit fullscreen mode

🏢 Real-World Example: E-commerce Product Filter

Here's a comprehensive example combining multiple operators:

import { filter, createConfig } from '@mcabreradev/filter';

interface Product {
  id: string;
  name: string;
  category: string;
  price: number;
  rating: number;
  tags: string[];
  inStock: boolean;
  brand: string;
  reviews: number;
}

const products: Product[] = [
  { id: '1', name: 'Gaming Laptop Pro', category: 'Electronics', price: 1299, rating: 4.7, tags: ['gaming', 'laptop', 'high-performance'], inStock: true, brand: 'TechCorp', reviews: 342 },
  { id: '2', name: 'Wireless Mouse', category: 'Accessories', price: 29, rating: 4.3, tags: ['wireless', 'ergonomic'], inStock: true, brand: 'TechCorp', reviews: 128 },
  { id: '3', name: 'Mechanical Keyboard', category: 'Accessories', price: 89, rating: 4.6, tags: ['mechanical', 'rgb', 'gaming'], inStock: false, brand: 'KeyMaster', reviews: 256 },
  { id: '4', name: 'USB-C Hub', category: 'Accessories', price: 45, rating: 4.1, tags: ['usb-c', 'hub'], inStock: true, brand: 'TechCorp', reviews: 89 },
];

// Complex product search with MongoDB-style operators
const searchResults = filter(products, {
  $and: [
    // Must be in stock
    { inStock: true },

    // Price range: $20 - $100
    { price: { $gte: 20, $lte: 100 } },

    // At least one matching tag
    {
      $or: [
        { tags: { $contains: 'gaming' } },
        { tags: { $contains: 'wireless' } },
        { tags: { $contains: 'ergonomic' } }
      ]
    },

    // Good ratings
    { rating: { $gte: 4.0 } },

    // Popular products
    { reviews: { $gt: 100 } }
  ]
});

// Result: [Wireless Mouse]
console.log(searchResults);
Enter fullscreen mode Exit fullscreen mode

⚡ Performance with Lazy Evaluation

For large datasets, use lazy evaluation to process items on-demand:

import { filterLazy } from '@mcabreradev/filter';

const hugeProductList = [...]; // 100,000 products

// Lazy evaluation - only processes until we find what we need
const firstExpensive = filterLazy(hugeProductList, {
  $and: [
    { price: { $gt: 1000 } },
    { rating: { $gte: 4.5 } },
    { inStock: true }
  ]
}).take(1);

// Only processes items until first match is found!
console.log(firstExpensive);
Enter fullscreen mode Exit fullscreen mode

🎨 Framework Integration

React Hook Example

import { useFilter } from '@mcabreradev/filter/react';

function ProductList() {
  const [products, setProducts] = useState<Product[]>([]);

  const { 
    filtered, 
    setQuery, 
    stats 
  } = useFilter(products, {
    debug: true,
    memoize: true
  });

  const searchExpensive = () => {
    setQuery({
      $and: [
        { price: { $gt: 500 } },
        { rating: { $gte: 4.0 } },
        { inStock: true }
      ]
    });
  };

  return (
    <div>
      <button onClick={searchExpensive}>
        Show Premium Products
      </button>
      <div>Found: {stats.totalProcessed} products</div>
      {filtered.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Vue Composable Example

import { useFilter } from '@mcabreradev/filter/vue';
import { ref } from 'vue';

export function useProductSearch() {
  const products = ref<Product[]>([]);

  const { filtered, setQuery, stats } = useFilter(products, {
    memoize: true
  });

  const searchByCategory = (category: string) => {
    setQuery({
      $and: [
        { category },
        { inStock: true },
        { rating: { $gte: 4.0 } }
      ]
    });
  };

  return {
    products: filtered,
    searchByCategory,
    stats
  };
}
Enter fullscreen mode Exit fullscreen mode

🐛 Debugging Your Queries

Enable debug mode to see exactly how your queries are processed:

import { filter, createConfig } from '@mcabreradev/filter';

const config = createConfig({
  debug: true,
  verbose: true
});

const results = filter(products, {
  $and: [
    { price: { $gt: 100 } },
    { tags: { $contains: 'gaming' } }
  ]
}, config);

// Console output:
// ┌─ Filter Execution Tree
// │ Root (AND)
// │  ├─ price.$gt(100) ✓
// │  └─ tags.$contains("gaming") ✓
// └─ Results: 1 item(s) in 0.234ms
Enter fullscreen mode Exit fullscreen mode

🔧 Custom Operators

Extend the library with your own MongoDB-style operators:

import { createConfig } from '@mcabreradev/filter';

const config = createConfig({
  customOperators: {
    $discounted: (value: number, threshold: number) => {
      const discount = (value.price - value.salePrice) / value.price;
      return discount >= threshold;
    },
    $trending: (value: Product) => {
      return value.views > 1000 && value.recentSales > 50;
    }
  }
});

const deals = filter(products, {
  $and: [
    { $discounted: 0.2 }, // 20% off or more
    { $trending: true }
  ]
}, config);
Enter fullscreen mode Exit fullscreen mode

📊 Comparison with Native Array Methods

// ❌ Native JavaScript - verbose and hard to read
const filtered = products
  .filter(p => p.price > 100 && p.price < 500)
  .filter(p => p.inStock)
  .filter(p => p.tags.includes('gaming') || p.tags.includes('professional'))
  .filter(p => p.rating >= 4.5);

// ✅ MongoDB-style - clean and declarative
const filtered = filter(products, {
  $and: [
    { price: { $gt: 100, $lt: 500 } },
    { inStock: true },
    { tags: { $in: ['gaming', 'professional'] } },
    { rating: { $gte: 4.5 } }
  ]
});
Enter fullscreen mode Exit fullscreen mode

🎯 Type Safety

Full TypeScript support with autocomplete:

// TypeScript knows your data structure
const filtered = filter<Product>(products, {
  // ✅ Autocomplete for properties
  price: { $gt: 100 },

  // ❌ TypeScript error - invalid property
  invalidProp: { $eq: 'test' },

  // ✅ Type-safe operators
  rating: { $gte: 4.0 },

  // ❌ TypeScript error - wrong operator for type
  price: { $contains: 'test' }
});
Enter fullscreen mode Exit fullscreen mode

🚀 Performance Tips

  1. Use memoization for repeated queries:
const config = createConfig({ memoize: true });
Enter fullscreen mode Exit fullscreen mode
  1. Lazy evaluation for large datasets:
const results = filterLazy(bigData, query).take(10);
Enter fullscreen mode Exit fullscreen mode
  1. Index frequently queried fields (coming soon in v3.0)

🔗 Resources

🎉 Try It Now!

npm install @mcabreradev/filter
Enter fullscreen mode Exit fullscreen mode

Start filtering your data with MongoDB-style operators today! The library is:

  • 100% TypeScript with full type safety
  • Zero dependencies
  • Tree-shakeable - only bundle what you use
  • Framework agnostic - works with React, Vue, Svelte, and more
  • Fully tested - 100% code coverage

💬 What's Next?

In the next post, I'll cover:

  • Advanced nested object filtering
  • Performance benchmarks vs other libraries
  • Building a full-stack search with MongoDB backend

Have questions or suggestions? Drop a comment below! 👇


If you found this helpful, give it a ❤️ and follow for more TypeScript tips!

Top comments (0)