Filtering Data

Filter your data with where clauses to find exactly what you need.

Quick Navigation

Simple Filtering

Filter by a single condition:

import { DataStore } from "@routier/datastore";
import { s } from "@routier/core/schema";
import { MemoryPlugin } from "@routier/memory-plugin";

const productSchema = s.define("products", {
    _id: s.string().key().identity(),
    name: s.string(),
    price: s.number(),
    category: s.string(),
    inStock: s.boolean(),
    tags: s.string("computer", "accessory").array(),
    createdDate: s.date().default(() => new Date())
}).compile();

class AppDataStore extends DataStore {
    products = this.collection(productSchema).create();
    constructor() {
        super(new MemoryPlugin("app"));
    }
}

const dataStore = new AppDataStore();

// Simple filtering
const expensiveProducts = await dataStore.products
    .where(p => p.price > 100)
    .toArrayAsync();

Multiple Conditions

Chain multiple where clauses for AND logic:

import { DataStore } from "@routier/datastore";
import { s } from "@routier/core/schema";
import { MemoryPlugin } from "@routier/memory-plugin";

const productSchema = s.define("products", {
    _id: s.string().key().identity(),
    name: s.string(),
    price: s.number(),
    category: s.string(),
    inStock: s.boolean(),
    tags: s.string("computer", "accessory").array(),
    createdDate: s.date().default(() => new Date())
}).compile();

class AppDataStore extends DataStore {
    products = this.collection(productSchema).create();
    constructor() {
        super(new MemoryPlugin("app"));
    }
}

const dataStore = new AppDataStore();

// Multiple where clauses (AND logic)
const expensiveElectronics = await dataStore.products
    .where(p => p.price > 100)
    .where(p => p.category === "electronics")
    .toArrayAsync();

Parameterized Queries

Use parameters for dynamic filtering with variables. This is required when you want to use variables in your query predicates.

Why Parameterized Queries?

When you need to use variables in your query, you must use parameterized queries. Direct variable usage in predicates will still work, but Routier will fall back to selecting all records because it cannot evaluate the variable values:

// ⚠️ This works but selects ALL records first, then filters in memory - less efficient
const minPrice = 100;
const maxPrice = 500;
const products = await dataStore.products
  .where((p) => p.price >= minPrice && p.price <= maxPrice) // Selects all, filters in memory
  .toArrayAsync();

Result: You’ll get the correct filtered results, but Routier will first load all records into memory, then apply the filter. This is less efficient than database-level filtering.

How Parameterized Queries Work

Pass variables through a parameters object:

import { DataStore } from "@routier/datastore";
import { s } from "@routier/core/schema";
import { MemoryPlugin } from "@routier/memory-plugin";

const productSchema = s.define("products", {
    _id: s.string().key().identity(),
    name: s.string(),
    price: s.number(),
    category: s.string(),
    inStock: s.boolean(),
    tags: s.string("computer", "accessory").array(),
    createdDate: s.date().default(() => new Date())
}).compile();

class AppDataStore extends DataStore {
    products = this.collection(productSchema).create();
    constructor() {
        super(new MemoryPlugin("app"));
    }
}

const dataStore = new AppDataStore();

// Parameterized queries
const minPrice = 50;
const maxPrice = 200;
const filteredProducts = await dataStore.products
    .where(([p, params]) => p.price >= params.minPrice && p.price <= params.maxPrice,
        { minPrice, maxPrice })
    .toArrayAsync();

Common Use Cases

Dynamic filtering based on user input:

const searchTerm = "laptop";
const category = "electronics";
const minPrice = 100;

const results = await dataStore.products
  .where(
    ([p, params]) =>
      p.name.toLowerCase().includes(params.searchTerm.toLowerCase()) &&
      p.category === params.category &&
      p.price >= params.minPrice,
    { searchTerm, category, minPrice }
  )
  .toArrayAsync();

Pagination with dynamic page size:

const page = 2;
const pageSize = 20;
const offset = (page - 1) * pageSize;

const products = await dataStore.products
  .where(([p, params]) => p.inStock === true, {})
  .skip(offset)
  .take(pageSize)
  .toArrayAsync();

Building Queries Conditionally

You can build queries dynamically by assigning query results back to a variable and chaining additional operations conditionally:

import { DataStore } from "@routier/datastore";
import { s } from "@routier/core/schema";
import { MemoryPlugin } from "@routier/memory-plugin";
import { InferType } from "@routier/core/schema";

const productSchema = s.define("products", {
    id: s.string().key().identity(),
    name: s.string(),
    category: s.string(),
    price: s.number(),
    inStock: s.boolean(),
}).compile();

type Product = InferType<typeof productSchema>;

class AppDataStore extends DataStore {
    products = this.collection(productSchema).create();
    constructor() {
        super(new MemoryPlugin("app"));
    }
}

const dataStore = new AppDataStore();

// Building a query dynamically based on logic
// In this example, we build a query by conditionally adding filters

const minPrice = 100;
const categoryFilter = "electronics";

// Convert collection to QueryableAsync for building queries dynamically
let query = dataStore.products.toQueryable();

// Build query conditionally - only add filters that apply
if (categoryFilter) {
    query = query.where(([p, params]) =>
        p.category === params.categoryFilter,
        { categoryFilter }
    );
}

if (minPrice > 0) {
    query = query.where(([p, params]) =>
        p.price >= params.minPrice,
        { minPrice }
    );
}

// Execute the built query
const results = await query.toArrayAsync();

// Pattern: Building queries with arrays using includes()
// When filtering by multiple ID values from an array
const productIds = ["prod-1", "prod-2", "prod-3"];
const productsByIds = await dataStore.products
    .where(([p, params]) =>
        params.ids.includes(p.id),
        { ids: productIds }
    )
    .toArrayAsync();

Key Pattern

Start with a base query and conditionally add filters:

// Start with base collection
let query = dataStore.products;

// Conditionally add filters based on logic
// Always use parameterized queries when using variables
if (shouldFilterByCategory) {
  query = query.where(([p, params]) => p.category === params.category, {
    category: "electronics",
  });
}

if (minPrice > 0) {
  query = query.where(([p, params]) => p.price >= params.minPrice, {
    minPrice,
  });
}

// Execute after building
const results = await query.toArrayAsync();

Filtering by Arrays

Use parameterized queries with includes() to filter by multiple values:

const productIds = ["prod-1", "prod-2", "prod-3"];

const products = await dataStore.products
  .where(([p, params]) => params.ids.includes(p.id), { ids: productIds })
  .toArrayAsync();

This pattern is especially useful when building queries in loops or based on conditional logic, as seen in Routier’s internal view computation system.

Notes

  • where supports either a simple predicate (item) => boolean or a parameterized predicate (item, params) => boolean with a params object
  • Use parameterized queries when you need variables - non-parameterized queries with variables will select all records and filter in memory (less efficient)
  • Multiple where clauses are combined with AND logic
  • For OR logic, use a single where with || operators inside the predicate