|
| 1 | +# Queries |
| 2 | + |
| 3 | +Monarch ORM provides a powerful, type-safe API for querying your MongoDB collections. By using the query builders, you can construct complex filters without losing type safety. |
| 4 | + |
| 5 | +## Query Modifiers and Immutability |
| 6 | + |
| 7 | +When you use `.find()` or `.findOne()`, Monarch returns a lazy query builder. The query is not executed until you `await` it. This allows you to chain modifiers to shape your results. |
| 8 | + |
| 9 | +> [!IMPORTANT] |
| 10 | +> Query modifier methods (`.select()`, `.sort()`, `.limit()`, etc.) **do not mutate** the original query instance. Instead, they return a **new** instance of the query builder. |
| 11 | +
|
| 12 | +This immutability makes it incredibly safe to re-use base queries throughout your application without unintended side effects. |
| 13 | + |
| 14 | +```typescript |
| 15 | +// Define a base query |
| 16 | +const activeUsersQuery = db.collections.users.find({ isVerified: true }); |
| 17 | + |
| 18 | +// Safely reuse the base query for different purposes |
| 19 | +const allActiveUsers = await activeUsersQuery; |
| 20 | + |
| 21 | +// This returns a NEW query instance, leaving `activeUsersQuery` untouched |
| 22 | +const paginatedUsers = await activeUsersQuery.limit(10).skip(20); |
| 23 | + |
| 24 | +// You can still use the original query without the limit/skip side effects |
| 25 | +const activeUserCount = await activeUsersQuery.count(); |
| 26 | +``` |
| 27 | + |
| 28 | +## Nested Fields and Dot Notation |
| 29 | + |
| 30 | +Because Monarch uses the official MongoDB driver's typings under the hood, you get full type safety when querying nested object fields using either dot notation or standard nested object structures. |
| 31 | + |
| 32 | +```typescript |
| 33 | +import { createSchema } from "monarch-orm"; |
| 34 | +import { object, string } from "monarch-orm/types"; |
| 35 | + |
| 36 | +// Define a schema with a nested object |
| 37 | +const UserSchema = createSchema("users", { |
| 38 | + name: string(), |
| 39 | + address: object({ |
| 40 | + city: string(), |
| 41 | + zip: string() |
| 42 | + }) |
| 43 | +}); |
| 44 | + |
| 45 | +// Using a standard nested object structure |
| 46 | +const usersInNewYorkNested = await db.collections.users.find({ |
| 47 | + address: { |
| 48 | + city: "New York" // fully typed! |
| 49 | + } |
| 50 | +}); |
| 51 | + |
| 52 | +// Or using dot notation |
| 53 | +const usersInNewYorkDot = await db.collections.users.find({ |
| 54 | + "address.city": "New York" |
| 55 | +}); |
| 56 | + |
| 57 | +``` |
| 58 | + |
| 59 | +The TypeScript compiler will correctly enforce the types for both nested properties and nested objects, ensuring you don't query a nested number field with a string or misspell the path (e.g., `"address.town"` or `{ address: { town: "..." } }` will throw a type error). |
| 60 | + |
| 61 | + |
| 62 | +## Selecting & Omitting Fields |
| 63 | + |
| 64 | +You can control exactly which fields are returned from the database using `.select()` and `.omit()`. |
| 65 | + |
| 66 | +```typescript |
| 67 | +// Only return the name and email fields |
| 68 | +const users = await db.collections.users |
| 69 | + .find() |
| 70 | + .select({ name: true, email: true }); |
| 71 | + |
| 72 | +// Return everything EXCEPT the age and password fields |
| 73 | +const publicUsers = await db.collections.users |
| 74 | + .find() |
| 75 | + .omit({ age: true, password: true }); |
| 76 | +``` |
| 77 | + |
| 78 | +## Sorting, Limiting, and Skipping |
| 79 | + |
| 80 | +Use these modifiers to paginate and order your results. |
| 81 | + |
| 82 | +```typescript |
| 83 | +const latestUsers = await db.collections.users |
| 84 | + .find({ isVerified: true }) |
| 85 | + .sort({ createdAt: -1 }) // Sort descending by createdAt |
| 86 | + .limit(10) // Return max 10 documents |
| 87 | + .skip(20); // Skip the first 20 documents |
| 88 | +``` |
| 89 | + |
| 90 | +## Cursors |
| 91 | + |
| 92 | +For large datasets where you don't want to load everything into memory at once, you can use `.cursor()` to iterate over documents one by one. |
| 93 | + |
| 94 | +```typescript |
| 95 | +const cursor = await db.collections.users.find({ isVerified: true }).cursor(); |
| 96 | + |
| 97 | +for await (const user of cursor) { |
| 98 | + console.log(user.name); |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +## Native MongoDB Syntax |
| 103 | + |
| 104 | +Because Monarch wraps the underlying MongoDB Node.js driver, you are still free to use native MongoDB query syntax if you prefer. The fields and operators are fully typed based on your schema. |
| 105 | + |
| 106 | +> [!TIP] |
| 107 | +> Alternatively, you can use our built-in [Helper Operators](/guide/operators) which provide even stronger type safety and schema inference. |
| 108 | +
|
| 109 | +```typescript |
| 110 | +const activeUsers = await db.collections.users.find({ |
| 111 | + age: { $gte: 18 }, |
| 112 | + role: { $in: ["admin", "moderator"] } |
| 113 | +}); |
| 114 | +``` |
0 commit comments