Queries

Routier queries are fluent and can only be performed through a collection. Build your query by chaining operations and finish with a terminal method to execute.

Quick Reference

Terminal Methods (Query Execution)

Method Description Example
toArrayAsync() Get all results as an array await ctx.products.toArrayAsync()
firstAsync() Get first item (throws if none) await ctx.products.firstAsync()
firstOrUndefinedAsync() Get first item or undefined await ctx.products.firstOrUndefinedAsync()
someAsync() Check if any items exist await ctx.products.someAsync()
countAsync() Count total items await ctx.products.countAsync()
sumAsync(field) Sum numeric field await ctx.products.sumAsync(p => p.price)
minAsync(field) Get minimum value await ctx.products.minAsync(p => p.price)
maxAsync(field) Get maximum value await ctx.products.maxAsync(p => p.price)
distinctAsync() Get unique values await ctx.products.distinctAsync()
toGroupAsync(selector) Group items by key await ctx.products.toGroupAsync(p => p.category)

Query Operations (Chaining)

Method Description Example
where(predicate) Filter results ctx.products.where(p => p.price > 100)
sort(field) Sort ascending ctx.products.sort(p => p.name)
orderByDescending(field) Sort descending ctx.products.orderByDescending(p => p.price)
map(selector) Transform/select fields ctx.products.map(p => ({ name: p.name, price: p.price }))
skip(count) Skip first N items ctx.products.skip(10)
take(count) Take first N items ctx.products.take(5)
subscribe() Enable live updates ctx.products.subscribe().toArray(callback)
defer() Skip initial query ctx.products.defer().subscribe().toArray(callback)

Detailed Examples

Getting All Results

// Get all products
const allProducts = await ctx.products.toArrayAsync();

Getting Single Items

// Get first product (throws if none exist)
const firstProduct = await ctx.products.firstAsync();

// Get first product or undefined if none exist
const firstOrUndefined = await ctx.products.firstOrUndefinedAsync();

Checking Existence

// Check if any products exist
const hasProducts = await ctx.products.someAsync();

// Check if all products are in stock
const allInStock = await ctx.products.everyAsync((p) => p.inStock);

Counting Items

// Count total products
const totalCount = await ctx.products.countAsync();

// Count products in specific category
const electronicsCount = await ctx.products
  .where((p) => p.category === "electronics")
  .countAsync();

Filtering Data

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

// Multiple filters
const activeElectronics = await ctx.products
  .where((p) => p.category === "electronics")
  .where((p) => p.inStock === true)
  .toArrayAsync();

// Parameterized filtering
const productsInRange = await ctx.products
  .where((p, params) => p.price >= params.min && p.price <= params.max, {
    min: 50,
    max: 200,
  })
  .toArrayAsync();

Sorting Results

// Sort by price (ascending)
const productsByPrice = await ctx.products.sort((p) => p.price).toArrayAsync();

// Sort by price (descending)
const expensiveFirst = await ctx.products
  .orderByDescending((p) => p.price)
  .toArrayAsync();

// Multiple sort criteria
const sortedProducts = await ctx.products
  .sort((p) => p.category)
  .sort((p) => p.name)
  .toArrayAsync();

Field Selection and Transformation

// Select specific fields to reduce data transfer
const productSummaries = await ctx.products
  .map((p) => ({
    id: p.id,
    name: p.name,
    price: p.price,
  }))
  .toArrayAsync();

// Create computed fields on-the-fly
const productsWithTax = await ctx.products
  .map((p) => ({
    id: p.id,
    name: p.name,
    price: p.price,
    priceWithTax: p.price * 1.1,
  }))
  .toArrayAsync();

Pagination

// Get first 10 products
const firstPage = await ctx.products.take(10).toArrayAsync();

// Get second page (skip first 10, take next 10)
const secondPage = await ctx.products.skip(10).take(10).toArrayAsync();

// Pagination helper
const pageSize = 10;
const pageNumber = 2; // 0-based
const page = await ctx.products
  .skip(pageSize * pageNumber)
  .take(pageSize)
  .toArrayAsync();

Aggregation Operations

// Sum prices of in-stock products
const totalValue = await ctx.products
  .where((p) => p.inStock === true)
  .sumAsync((p) => p.price);

// Get minimum and maximum prices
const minPrice = await ctx.products.minAsync((p) => p.price);
const maxPrice = await ctx.products.maxAsync((p) => p.price);

// Get distinct categories
const categories = await ctx.products.map((p) => p.category).distinctAsync();

Complex Queries

// Complex query with multiple operations
const topExpensiveElectronics = await ctx.products
  .where((p) => p.category === "electronics")
  .where((p) => p.inStock === true)
  .orderByDescending((p) => p.price)
  .take(5)
  .map((p) => ({
    name: p.name,
    price: p.price,
    priceWithTax: p.price * 1.1,
  }))
  .toArrayAsync();

Key Concepts

Query Execution

  • Lazy evaluation: Queries don’t execute until you call a terminal method
  • Chaining: You can chain multiple operations together
  • Collection-based: All queries must start with a collection

Performance Tips

  • Database filters first: Apply where clauses on database fields before computed fields
  • Limit results: Use take() to limit large result sets
  • Efficient pagination: Use skip() and take() for pagination

Computed Properties

When filtering on computed properties (not stored in database), the filter runs in memory:

// Good: Database-backed filter first
const expensiveElectronics = await ctx.products
  .where((p) => p.category === "electronics") // Database filter
  .where((p) => p.isExpensive === true) // Computed filter
  .toArrayAsync();

// Less efficient: Computed filter first
const allExpensive = await ctx.products
  .where((p) => p.isExpensive === true) // Loads all records
  .where((p) => p.category === "electronics") // Then filters
  .toArrayAsync();

Table of contents