Skip to content

Indexes

Indexes improve query performance by allowing MongoDB to find documents without scanning the entire collection.

Defining Indexes

typescript
const Users = collection({
  name: 'users',
  schema: {
    _id: field.id(),
    email: field.email(),
    name: field.string(),
    role: field.enum(['user', 'admin']),
    status: field.enum(['active', 'inactive']),
    createdAt: field.datetime(),
  },
  indexes: [
    // Simple index
    { key: { email: 1 } },

    // Unique index
    { key: { email: 1 }, unique: true },

    // Compound index
    { key: { role: 1, createdAt: -1 } },

    // Descending index
    { key: { createdAt: -1 } },
  ],
});

Index Types

Single Field Index

typescript
{ key: { email: 1 } }   // Ascending
{ key: { createdAt: -1 } }  // Descending

Compound Index

Index on multiple fields (order matters):

typescript
{ key: { status: 1, createdAt: -1 } }

This index supports queries like:

  • { status: 'active' }
  • { status: 'active', createdAt: { $gte: date } }
  • Sorting by { status: 1, createdAt: -1 }

Unique Index

Enforce unique values:

typescript
{ key: { email: 1 }, unique: true }

Attempting to insert a duplicate throws an error.

Sparse Index

Only index documents that have the field:

typescript
{ key: { optionalField: 1 }, sparse: true }

Useful for optional fields where you want unique constraint only on documents that have the field.

Partial Index

Index only documents matching a filter:

typescript
{
  key: { email: 1 },
  partialFilterExpression: { status: 'active' }
}

Only indexes documents where status === 'active'.

Text Index

Enable full-text search:

typescript
{ key: { title: 'text', content: 'text' } }

Query with:

typescript
Users.find({ $text: { $search: 'mongodb tutorial' } });

TTL Index

Auto-delete documents after a time period:

typescript
{
  key: { expiresAt: 1 },
  expireAfterSeconds: 0  // Delete when expiresAt is reached
}

// Or delete after fixed duration
{
  key: { createdAt: 1 },
  expireAfterSeconds: 86400  // Delete 24 hours after creation
}

Wildcard Index

Index all fields (use sparingly):

typescript
{ key: { '$**': 1 } }

// Or specific path
{ key: { 'metadata.$**': 1 } }

Index Options

OptionTypeDescription
uniquebooleanEnforce unique values
sparsebooleanOnly index documents with the field
backgroundbooleanBuild index in background (deprecated in 4.2+)
expireAfterSecondsnumberTTL - auto-delete after seconds
partialFilterExpressionobjectOnly index matching documents
namestringCustom index name
collationobjectLocale-specific string comparison

Auto-Creation

By default, Monch creates indexes on first connection:

typescript
const Users = collection({
  name: 'users',
  schema: { /* ... */ },
  indexes: [
    { key: { email: 1 }, unique: true },
  ],
  createIndexes: true,  // Default: true
});

Disable auto-creation:

typescript
const Users = collection({
  name: 'users',
  schema: { /* ... */ },
  indexes: [ /* ... */ ],
  createIndexes: false,  // Don't auto-create
});

Manual Index Creation

Create indexes explicitly:

typescript
// Create all configured indexes
const indexNames = await Users.ensureIndexes();
console.log('Created indexes:', indexNames);

Common Index Patterns

Email Lookup

typescript
indexes: [
  { key: { email: 1 }, unique: true },
]

Status + Date Queries

typescript
// For queries like: { status: 'active' } sorted by createdAt
indexes: [
  { key: { status: 1, createdAt: -1 } },
]

User Activity

typescript
indexes: [
  { key: { userId: 1, createdAt: -1 } },  // User's activity timeline
  { key: { createdAt: -1 } },             // Global activity feed
]

Search with Filters

typescript
indexes: [
  { key: { title: 'text', content: 'text' } },  // Text search
  { key: { category: 1, createdAt: -1 } },      // Category browse
]

Geo-Spatial

typescript
// For location-based queries
indexes: [
  { key: { location: '2dsphere' } },
]

// Usage
Users.find({
  location: {
    $near: {
      $geometry: { type: 'Point', coordinates: [-73.9667, 40.78] },
      $maxDistance: 1000,  // meters
    },
  },
});

Best Practices

1. Index Fields Used in Queries

typescript
// If you query like this:
Users.find({ role: 'admin', status: 'active' });

// Create this index:
{ key: { role: 1, status: 1 } }

2. Consider Query Patterns

Order compound index fields by:

  1. Equality conditions first
  2. Sort fields second
  3. Range conditions last
typescript
// Query: { status: 'active', role: 'admin' } sorted by createdAt
// Index: status (equality) + role (equality) + createdAt (sort)
{ key: { status: 1, role: 1, createdAt: -1 } }

3. Don't Over-Index

  • Each index uses memory and disk space
  • Indexes slow down writes (must update indexes)
  • Remove unused indexes

4. Use Covered Queries

If an index contains all fields needed by a query, MongoDB can return results from the index alone:

typescript
// Index
{ key: { email: 1, name: 1 } }

// This query is "covered" - only needs the index
Users.find({ email: 'alice@example.com' })
  .project({ email: 1, name: 1, _id: 0 });

5. Monitor Index Usage

Use MongoDB tools to analyze index usage:

javascript
// In MongoDB shell
db.users.aggregate([{ $indexStats: {} }]);

Viewing Existing Indexes

Access the raw collection to view indexes:

typescript
const raw = await Users.collection;
const indexes = await raw.indexes();
console.log(indexes);

Released under the MIT License.