Skip to main content
Once you have your models defined, querying your database becomes incredibly straightforward! Each Esix model acts as a powerful query builder, allowing you to fluently search and retrieve documents from your MongoDB collection.

Finding a Single Record

When you need to find a specific document by its ID, use the find method:
const book = await Book.find(22)
{
  id: '5f5a474b32fa462a5724ff7d',
  title: 'Effective TypeScript'
}
find accepts both ObjectId hex strings and plain string ids. If the value is not a valid 24-character hex string, Esix transparently falls back to a string _id lookup. Invalid input never throws — find just returns null when no matching document exists. You can also find a model by a specific field using the findBy method:
const user = await User.findBy('email', 'john@example.com')
{
  id: '5f5a4c36493d53b6caa8410e',
  name: 'John Doe',
  email: 'john@example.com'
}
To get the first model that matches your query conditions, use the first method:
const latestPost = await BlogPost.where('status', 'published')
  .orderBy('createdAt', 'desc')
  .first()
{
  id: '6011a52b9f1b2c4d8e7f3a21',
  title: 'Introducing Aggregate Functions',
  status: 'published',
  createdAt: 1736380800000
}
You can also get all the models in the collection.
const flights = await Flight.all()
idname
5f5a474b32fa462a5724ff7dAA 100 → JFK
5f5a474b32fa462a5724ff7eBA 286 → LHR
5f5a474b32fa462a5724ff7fUA 245 → SFO
When you are working with multiple models, you can use methods like where which returns an instance of a QueryBuilder. The Query Builder can be used to filter, sort, and limit your searches.
const blogPosts = await BlogPost.where('status', 'published')
  .where('categoryId', 4)
  .orderBy('publishedAt', 'desc')
  .limit(12)
  .get()
idtitlestatuscategoryIdpublishedAt
6011a52b9f1b2c4d8e7f3a21Introducing Aggregate Functionspublished41736380800000
60119e8a9f1b2c4d8e7f3a14Querying With Comparison Operatorspublished41735862400000
601198119f1b2c4d8e7f3a09Pagination Patterns in Esixpublished41734998400000

Comparison Operators

The where method supports comparison operators for numeric and date comparisons, similar to Laravel’s Eloquent:
// Greater than
const adults = await User.where('age', '>', 18).get()

// Greater than or equal
const eligibleVoters = await User.where('age', '>=', 18).get()

// Less than
const youngUsers = await User.where('age', '<', 30).get()

// Less than or equal
const affordableProducts = await Product.where('price', '<=', 100).get()

// Equals (explicit)
const exactMatch = await Product.where('price', '=', 49.99).get()

// Not equals
const activeUsers = await User.where('status', '!=', 'banned').get()
const alsActive = await User.where('status', '<>', 'banned').get() // alternative syntax
Each call above returns an array of matching records. For example, adults looks like:
idnameagestatus
5f5a474b32fa462a5724ff7dAlice32active
5f5a474b32fa462a5724ff7eBob45active
5f5a474b32fa462a5724ff7fCarol28active
You can chain multiple comparison operators together:
// Users between 18 and 65 years old
const workingAgeUsers = await User
  .where('age', '>=', 18)
  .where('age', '<=', 65)
  .get()

// Products in a price range
const affordableProducts = await Product
  .where('price', '>', 10)
  .where('price', '<', 100)
  .where('inStock', true)
  .get()

// Posts with many views
const popularPosts = await BlogPost
  .where('views', '>', 1000)
  .where('status', 'published')
  .orderBy('views', 'desc')
  .get()
workingAgeUsers
idnameagestatus
5f5a474b32fa462a5724ff7dAlice32active
5f5a474b32fa462a5724ff7eBob45active
5f5a474b32fa462a5724ff80Dimitri64active
affordableProducts
idnamepriceinStock
60119e8a9f1b2c4d8e7f3a14Desk Lamp24.99true
60119e8a9f1b2c4d8e7f3a15Floor Lamp79.00true
60119e8a9f1b2c4d8e7f3a16Reading Lamp39.50true
popularPosts
idtitleviewsstatus
6011a52b9f1b2c4d8e7f3a21Introducing Aggregate Functions12_840published
60119e8a9f1b2c4d8e7f3a14Comparison Operators Are Here6_120published
601198119f1b2c4d8e7f3a09Pagination Patterns in Esix3_402published

Supported Operators

OperatorDescriptionExample
=Equals.where('age', '=', 25)
!=Not equals.where('status', '!=', 'banned')
<>Not equals (alternate).where('status', '<>', 'banned')
>Greater than.where('age', '>', 18)
>=Greater than or equal.where('score', '>=', 100)
<Less than.where('age', '<', 65)
<=Less than or equal.where('price', '<=', 50)
Note: The two-parameter syntax where('status', 'published') is still supported for equality comparisons and remains the recommended approach for simple equality checks.

Array Queries

You can use whereIn to retrieve models where a column’s value is within a given array:
const users = await User.whereIn('id', [1, 2, 3]).get()
idnameagestatus
1Alice32active
2Bob45active
3Carol28inactive
Conversely, you can use whereNotIn to retrieve models where a column’s value is not within a given array:
const users = await User.whereNotIn('id', [1, 2, 3]).get()
idnameagestatus
4Dimitri64active
5Eli19active
6Farah37active
If you are only interested in a single attribute of a model, you can use the pluck method to get an array of values for that attribute.
const productNames = await Product.where('category', 'lamps').pluck('name')
['Desk Lamp', 'Floor Lamp', 'Reading Lamp']

Distinct Values

Use distinct to get the unique values of a field across the current query:
const tags = await Post.where('published', true).distinct('tag')
['mongodb', 'pagination', 'typescript']
The result is a deduplicated array of values for the field, respecting any active where constraints. Once your collection has a text index, use search to run full-text queries:
const results = await Post.search('mongodb typescript').get()
idtitlepublishedtag
6011a52b9f1b2c4d8e7f3a21Querying MongoDB With TypeScripttruetypescript
60119e8a9f1b2c4d8e7f3a14Why MongoDB Aggregations Mattertruemongodb
601198119f1b2c4d8e7f3a09Typed Schemas for MongoDBtruemongodb
If the collection has no text index, Esix surfaces a descriptive error explaining how to create one.

Pagination

The fastest way to paginate is paginate(page, perPage), which returns the page of models alongside the metadata you need to render pagination UIs:
const { data, total, page, perPage, lastPage } = await Post
  .where('published', true)
  .paginate(1, 20)
{
  data: [/* 20 Post records */],
  total: 137,
  page: 1,
  perPage: 20,
  lastPage: 7
}
For more control, you can fall back to manual offset pagination using skip and limit:
const page = 2
const perPage = 10
const offset = (page - 1) * perPage

const products = await Product.where('category', 'electronics')
  .skip(offset)
  .limit(perPage)
  .get()
idnamecategoryprice
60119e8a9f1b2c4d8e7f3a21Bluetooth Speakerelectronics59.00
60119e8a9f1b2c4d8e7f3a22Smart Bulbelectronics14.99
60119e8a9f1b2c4d8e7f3a23USB-C Hubelectronics34.50
You can find out more about the different methods available by consulting the Esix source on GitHub.

Aggregate Functions

Once you are happy with your query, you can use the aggregate functions available in Esix to perform calculations on the data set. The supported aggregates are average, count, max, min, percentile, and sum.
await Product.where('category', 'lamps').average('price')

await Product.where('category', 'lamps').count()

await Product.where('category', 'lamps').max('price')

await Product.where('category', 'lamps').min('price')

await Product.where('category', 'lamps').percentile('price', 50)

await Product.where('category', 'lamps').sum('price')
45.99
12
199.99
9.99
39.50
551.88
When the query matches no documents, the numeric aggregates (average, max, min, percentile, sum) return 0 rather than throwing. percentile requires n to be a finite number between 0 and 100. Any other value (including NaN and Infinity) throws a descriptive error so that bad inputs do not silently return misleading results.