β

ObjectQL v4.0 is currently in Beta.

ObjectStack LogoObjectQL
Data Access

Modern Filter Syntax Guide

ObjectQL now supports a modern, intuitive object-based filter syntax inspired by MongoDB, Prisma, and other leading ORMs. This guide covers the new filter syntax and migration from the legacy array-based format.

Overview

Old Syntax (Legacy - Still Supported)

const results = await repo.find({
  filters: [
    ['status', '=', 'active'],
    'and',
    ['price', '>', 100]
  ]
});
const results = await repo.find({
  filters: {
    status: 'active',
    price: { $gt: 100 }
  }
});

Basic Filters

Implicit Equality

The simplest form - just specify field and value:

// Find products in Electronics category
const results = await repo.find({
  filters: { category: 'Electronics' }
});

// Multiple conditions (implicit AND)
const results = await repo.find({
  filters: {
    category: 'Electronics',
    status: 'active'
  }
});

Explicit Equality ($eq)

const results = await repo.find({
  filters: {
    status: { $eq: 'active' }
  }
});

Comparison Operators

Equality and Inequality

// Equal
{ status: { $eq: 'active' } }

// Not equal
{ status: { $ne: 'inactive' } }

Numeric and Date Comparisons

// Greater than
{ price: { $gt: 100 } }

// Greater than or equal
{ price: { $gte: 100 } }

// Less than
{ price: { $lt: 500 } }

// Less than or equal
{ price: { $lte: 500 } }

// Range query (combines operators)
{
  price: {
    $gte: 100,
    $lte: 500
  }
}

// Date comparisons
{
  createdAt: {
    $gte: new Date('2024-01-01'),
    $lt: new Date('2024-12-31')
  }
}

Set Membership

// In array
{
  status: { $in: ['active', 'pending', 'processing'] }
}

// Not in array
{
  status: { $nin: ['inactive', 'deleted'] }
}

String Operators

// Contains substring (case-sensitive)
{
  name: { $contains: 'laptop' }
}

// Starts with
{
  name: { $startsWith: 'Apple' }
}

// Ends with
{
  email: { $endsWith: '@company.com' }
}

Null and Existence Checks

// Is null
{
  deletedAt: { $null: true }
}

// Is not null
{
  deletedAt: { $null: false }
}

// Field exists (primarily for NoSQL)
{
  optionalField: { $exist: true }
}

Logical Operators

AND (Explicit)

// Explicit AND
const results = await repo.find({
  filters: {
    $and: [
      { category: 'Electronics' },
      { status: 'active' },
      { price: { $gt: 100 } }
    ]
  }
});

// Note: Top-level fields are implicitly AND-ed
// These are equivalent:
{ category: 'Electronics', status: 'active' }
{ $and: [{ category: 'Electronics' }, { status: 'active' }] }

OR

const results = await repo.find({
  filters: {
    $or: [
      { category: 'Electronics' },
      { category: 'Computers' },
      { featured: true }
    ]
  }
});

NOT

Note: The $not operator is not currently supported when using the backward-compatible translation layer. Use $ne (not equal) for field-level negation instead.

// ❌ Not supported
const results = await repo.find({
  filters: {
    $not: { status: 'deleted' }
  }
});

// ✅ Use $ne instead
const results = await repo.find({
  filters: {
    status: { $ne: 'deleted' }
  }
});

Complex Nested Logic

// (category = 'Electronics' OR category = 'Computers') 
// AND status = 'active' 
// AND price > 100
const results = await repo.find({
  filters: {
    $and: [
      {
        $or: [
          { category: 'Electronics' },
          { category: 'Computers' }
        ]
      },
      { status: 'active' },
      { price: { $gt: 100 } }
    ]
  }
});

Real-World Examples

// Find available products in price range
const products = await repo.find({
  filters: {
    status: 'active',
    stock: { $gt: 0 },
    price: {
      $gte: 50,
      $lte: 200
    },
    category: { $in: ['Electronics', 'Computers', 'Accessories'] }
  }
});

User Management

// Find active users created in the last 30 days
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);

const recentUsers = await repo.find({
  filters: {
    status: 'active',
    emailVerified: true,
    createdAt: { $gte: thirtyDaysAgo },
    $or: [
      { role: 'premium' },
      { trialExpires: { $null: false } }
    ]
  }
});

Task Management

// Find high-priority tasks assigned to team or overdue
const urgentTasks = await repo.find({
  filters: {
    $or: [
      { priority: 'high' },
      { dueDate: { $lt: new Date() } }
    ],
    status: { $nin: ['completed', 'cancelled'] },
    assignedTo: { $in: teamMemberIds }
  }
});

Operator Reference Table

OperatorDescriptionExample
(implicit)Equal to{ status: 'active' }
$eqEqual to{ status: { $eq: 'active' } }
$neNot equal to{ status: { $ne: 'deleted' } }
$gtGreater than{ price: { $gt: 100 } }
$gteGreater than or equal{ price: { $gte: 100 } }
$ltLess than{ price: { $lt: 500 } }
$lteLess than or equal{ price: { $lte: 500 } }
$inIn array{ status: { $in: ['active', 'pending'] } }
$ninNot in array{ status: { $nin: ['deleted'] } }
$containsContains substring{ name: { $contains: 'pro' } }
$startsWithStarts with{ name: { $startsWith: 'Apple' } }
$endsWithEnds with{ email: { $endsWith: '@gmail.com' } }
$nullIs null (true) or not null (false){ deletedAt: { $null: true } }
$existField exists{ metadata: { $exist: true } }
$andLogical AND{ $and: [{...}, {...}] }
$orLogical OR{ $or: [{...}, {...}] }

Migration Guide

Simple Equality

// Old
filters: [['status', '=', 'active']]

// New
filters: { status: 'active' }

Multiple Conditions (AND)

// Old
filters: [
  ['category', '=', 'Electronics'],
  'and',
  ['status', '=', 'active']
]

// New
filters: {
  category: 'Electronics',
  status: 'active'
}

Comparison Operators

// Old
filters: [['price', '>', 100]]

// New
filters: { price: { $gt: 100 } }

OR Conditions

// Old
filters: [
  ['category', '=', 'Electronics'],
  'or',
  ['category', '=', 'Furniture']
]

// New
filters: {
  $or: [
    { category: 'Electronics' },
    { category: 'Furniture' }
  ]
}

Complex Nested Filters

// Old
filters: [
  [
    ['category', '=', 'Electronics'],
    'or',
    ['category', '=', 'Furniture']
  ],
  'and',
  ['status', '=', 'active']
]

// New
filters: {
  $and: [
    {
      $or: [
        { category: 'Electronics' },
        { category: 'Furniture' }
      ]
    },
    { status: 'active' }
  ]
}

TypeScript Support

The new filter syntax is fully type-safe:

import { Filter } from '@objectql/types';

// Define a typed filter
interface Product {
  name: string;
  price: number;
  category: string;
  inStock: boolean;
}

const productFilter: Filter<Product> = {
  category: 'Electronics',
  price: { $gte: 100, $lte: 500 },
  inStock: true
};

const results = await repo.find({ filters: productFilter });

Best Practices

1. Use Implicit Equality for Simple Conditions

// ✅ Good
{ status: 'active' }

// ❌ Unnecessary
{ status: { $eq: 'active' } }
// ✅ Good - Price range clearly grouped
{
  category: 'Electronics',
  price: {
    $gte: 100,
    $lte: 500
  }
}

// ❌ Less clear
{
  category: 'Electronics',
  $and: [
    { price: { $gte: 100 } },
    { price: { $lte: 500 } }
  ]
}

3. Use $in for Multiple Values

// ✅ Good
{ status: { $in: ['active', 'pending', 'processing'] } }

// ❌ Verbose
{
  $or: [
    { status: 'active' },
    { status: 'pending' },
    { status: 'processing' }
  ]
}

4. Keep Filters Readable

// ✅ Good - Extract complex filters to variables
const priceRange = { $gte: 100, $lte: 500 };
const activeCategories = ['Electronics', 'Computers'];

const filter = {
  price: priceRange,
  category: { $in: activeCategories },
  status: 'active'
};

const results = await repo.find({ filters: filter });

Performance Considerations

  1. Index Your Fields: Ensure fields used in filters are indexed in your database
  2. Use Specific Operators: Use $in instead of multiple $or conditions when possible
  3. Limit Range Queries: Range queries on non-indexed fields can be slow
  4. Avoid $not When Possible: Negative conditions can prevent index usage in some databases

Backward Compatibility

The legacy array-based syntax is still fully supported for existing code:

// This still works
const results = await repo.find({
  filters: [['status', '=', 'active']]
});

However, new code should use the modern object-based syntax for better readability and type safety.

On this page