Query Builder
With the query-builder plugin you can write RxQuery selectors with chained methods instead of passing a JSON object. Each chained call adds one condition, and the result is a normal RxQuery with a normal Mango query underneath. This means everything on the RxQuery page (exec(), $, update(), remove(), indexes, the query cache) works the same for chained queries.
All examples on this page use the todos collection and the example documents from the example schema.
Add the Plugin
import { addRxPlugin } from 'rxdb';
import { RxDBQueryBuilderPlugin } from 'rxdb/plugins/query-builder';
addRxPlugin(RxDBQueryBuilderPlugin);Now you can chain query methods on any find() or findOne() call:
// selector: { priority: { $gt: 5 } }
const query = myDatabase.todos.find().where('priority').gt(5);
const results = await query.exec();
// > [RxDocument[todo1], RxDocument[todo3]]Selecting a Field with where()
where(path) sets the field that the following operator method applies to. The selected field stays active until the next where() call, so you can chain multiple operators on the same field.
// selector: { priority: { $gte: 3, $lte: 8 }, done: { $eq: false } }
const results = await myDatabase.todos.find()
.where('priority').gte(3).lte(8)
.where('done').eq(false)
.exec();
// > [RxDocument[todo1]]Nested fields are selected with the dot notation:
// selector: { 'assignee.name': { $eq: 'Alice' } }
const results = await myDatabase.todos.find()
.where('assignee.name').eq('Alice')
.exec();
// > [RxDocument[todo1]]You can also pass a plain selector object to where(). It is merged into the existing selector:
// selector: { done: false, priority: { $gt: 5 } }
const results = await myDatabase.todos.find()
.where({
done: false,
priority: { $gt: 5 }
})
.exec();
// > [RxDocument[todo1], RxDocument[todo3]]Comparison Operators
Each operator method maps to the mango operator with the same name:
| Method | Mango operator | Example | Result |
|---|---|---|---|
eq(value) / equals(value) | $eq | .where('done').eq(true) | todo2 |
ne(value) | $ne | .where('done').ne(true) | todo1, todo3, todo4 |
gt(value) | $gt | .where('priority').gt(5) | todo1, todo3 |
gte(value) | $gte | .where('priority').gte(8) | todo1, todo3 |
lt(value) | $lt | .where('priority').lt(3) | todo4 |
lte(value) | $lte | .where('priority').lte(3) | todo2, todo4 |
in(array) | $in | .where('tags').in(['home', 'backend']) | todo2, todo3 |
nin(array) | $nin | .where('tags').nin(['rxdb']) | todo2, todo4 |
exists(boolean) | $exists | .where('note').exists(true) | todo2 |
regex(value) | $regex | .where('name').regex('.*RxDB.*') | todo1 |
mod([divisor, remainder]) | $mod | .where('priority').mod([2, 0]) | todo1, todo3 |
On an array field like tags, in() matches when at least one element of the array is in the given list, and nin() matches when no element is in the list.
exists(false) finds documents where an optional field is missing:
// selector: { assignee: { $exists: false } }
const results = await myDatabase.todos.find()
.where('assignee').exists(false)
.exec();
// > [RxDocument[todo3], RxDocument[todo4]]Notice that on an RxQuery, each chained method takes exactly one argument and applies to the field that was selected with where() before. The two-argument forms like .gt('priority', 5) or .exists('note') are not supported. Always select the field with where() first.
Regex Queries
regex() takes a regex string, or an object with $regex and $options when you need flags. Passing a RegExp instance throws an error, because a RegExp is not JSON-serializable and is mutable.
// sql equivalent of LIKE '%RxDB%'
const results = await myDatabase.todos.find()
.where('name').regex('.*RxDB.*')
.exec();
// > [RxDocument[todo1]]
// case insensitive: matches 'learn rxdb', 'LEARN RXDB', 'Learn RxDB' and so on
const caseInsensitiveResults = await myDatabase.todos.find()
.where('name').regex({ $regex: '^learn rxdb$', $options: 'i' })
.exec();
// > [RxDocument[todo1]]Array Fields
size() matches arrays with an exact number of elements:
// selector: { tags: { $size: 2 } }
const results = await myDatabase.todos.find()
.where('tags').size(2)
.exec();
// > [RxDocument[todo1], RxDocument[todo3]]elemMatch() matches documents where at least one element of the array fulfills all given conditions:
// todos with at least one tag that starts with 'back'
// selector: { tags: { $elemMatch: { $regex: '^back' } } }
const results = await myDatabase.todos.find()
.where('tags').elemMatch({ $regex: '^back' })
.exec();
// > [RxDocument[todo3]]For arrays of objects, elemMatch() also accepts a function instead of a selector. The function receives a nested query builder, so the element conditions can be chained with where() as well: .where('items').elemMatch(elem => { elem.where('name').eq('foo'); }).
To find documents where an array contains multiple values, use and() with one condition per value:
// todos that have both the 'rxdb' and the 'learning' tag
// selector: { $and: [ { tags: 'rxdb' }, { tags: 'learning' } ] }
const results = await myDatabase.todos.find()
.and([
{ tags: 'rxdb' },
{ tags: 'learning' }
])
.exec();
// > [RxDocument[todo1]]Logical Operators
or(), and() and nor() take an array of selectors and add them to the $or, $and and $nor lists of the query:
// todos that are done or have a priority of 9 or higher
// selector: { $or: [ { done: true }, { priority: { $gte: 9 } } ] }
const results = await myDatabase.todos.find()
.or([
{ done: true },
{ priority: { $gte: 9 } }
])
.exec();
// > [RxDocument[todo2], RxDocument[todo3]]They can be combined with where() conditions:
// open todos that are neither tagged 'home' nor have a priority below 2
/**
* selector: {
* done: { $eq: false },
* $nor: [ { tags: 'home' }, { priority: { $lt: 2 } } ]
* }
*/
const results = await myDatabase.todos.find()
.where('done').eq(false)
.nor([
{ tags: 'home' },
{ priority: { $lt: 2 } }
])
.exec();
// > [RxDocument[todo1], RxDocument[todo3]]Sorting
sort() accepts a space-separated string where a - prefix means descending, or an object with 'asc', 'desc', 1 or -1 as values. Both queries below produce the sort [{ priority: 'desc' }, { name: 'asc' }]:
const results = await myDatabase.todos.find()
.sort('-priority name')
.exec();
// > [RxDocument[todo3], RxDocument[todo1], RxDocument[todo2], RxDocument[todo4]]
const sameResults = await myDatabase.todos.find()
.sort({ priority: 'desc', name: 'asc' })
.exec();Same as with JSON queries, RxDB appends the primary key as last sort field to get a deterministic order.
Limit and Skip
limit() and skip() set the number of returned documents and the offset. Together with sort() this is the way to paginate:
// the second page with 2 todos per page
const results = await myDatabase.todos.find()
.sort('-priority')
.skip(2)
.limit(2)
.exec();
// > [RxDocument[todo2], RxDocument[todo4]]The chained methods also work with findOne():
// the todo with the highest priority above 5
const doc = await myDatabase.todos.findOne()
.where('priority').gt(5)
.sort('-priority')
.exec();
// > RxDocument[todo3]Chaining on an Existing Query
The query builder methods also work on queries that were created with a JSON selector. Each call returns a new RxQuery and does not modify the original one, because RxQuery instances are immutable. This makes it possible to derive multiple queries from a base query:
const openTodosQuery = myDatabase.todos.find({
selector: {
done: false
}
});
// selector: { done: false, priority: { $gte: 8 } }
const urgentOpenTodos = await openTodosQuery
.where('priority').gte(8)
.exec();
// > [RxDocument[todo1], RxDocument[todo3]]
// openTodosQuery is unchangedFull Example
The following example combines most of the methods. The comment shows the mango query that RxDB runs under the hood:
/**
* {
* selector: {
* done: { $eq: false },
* priority: { $gte: 5 },
* tags: { $in: ['rxdb'] },
* $or: [ { 'assignee.name': 'Alice' }, { note: { $exists: false } } ]
* },
* sort: [ { priority: 'desc' } ],
* limit: 10
* }
*/
const query = myDatabase.todos.find()
.where('done').eq(false)
.where('priority').gte(5)
.where('tags').in(['rxdb'])
.or([
{ 'assignee.name': 'Alice' },
{ note: { $exists: false } }
])
.sort('-priority')
.limit(10);
// emits the current results and again whenever they change
query.$.subscribe(todos => console.dir(todos));
// > [RxDocument[todo3], RxDocument[todo1]]You can always inspect the generated mango query with query.mangoQuery. This helps when a chained query does not return the documents you expect.