Definining indexes with MongoDB and Mongoose
The bigger our database is, the more demanding our queries become in terms of computing power. A common way of tackling this problem is by creating indexes. In…
The bigger our database is, the more demanding our queries become in terms of computing power. A common way of tackling this problem is by creating indexes. In this article, we explore this concept and create indexes with MongoDB and Mongoose. When performing a MongoDB query, the database must scan every document in a given collection to find matching documents. MongoDB can limit the number of records to inspect if we have an appropriate index in our database. Since it makes it easier to search for the documents in the database, indexes can speed up finding, updating, and deleting. Under the hood, indexes are data structures that store a small part of the collection’s data in an easy-to-traverse way. It includes the ordered values of a particular field of the documents. It makes MongoDB indexes similar to indexes in databases such as PostgreSQL. When we define indexes, MongoDB needs to store additional data to speed up our queries. But, unfortunately, it slows down our write queries. It also takes up more memory. Therefore, we need to create indexes sparingly. Unique indexes The unique index makes sure that we don’t store duplicate values. We can create it by passing unique: true to the @Prop decorator. user.schema.ts import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Document, ObjectId } from 'mongoose'; import { Transform } from 'class-transformer'; export type UserDocument = User & Document; @Schema({ toJSON: { getters: true, virtuals: true, }, }) export class User { @Transform(({ value }) => value.toString()) _id: ObjectId; @Prop({ unique: true }) email: string; // ... } const UserSchema = SchemaFactory.createForClass(User);It is important to know that MongoDB creates a unique index on the _id field when creating a collection. Therefore, we sometimes refer to it as the primary index. We take advantage of the above in the last part of this series, where we implement pagination and sort documents by the _id field. When we sort documents using a field without an index, MongoDB performs sorting at query time. It takes time and resources to do that and makes our app response slower. However, having the right index can help us avoid sorting results at query time because the results are already sorted in the index. Therefore, we can return them immediately. We need to keep in mind that making a property unique creates an index and slows down our write queries. Implementing indexes with Mongoose With MongoDB, we can also define secondary indexes that don’t make properties unique. post.schema.ts import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Document, ObjectId } from 'mongoose'; import { Transform, Type } from 'class-transformer'; export type PostDocument = Post & Document; @Schema() export class Post { @Transform(({ value }) => value.toString()) _id: ObjectId; @Prop({ index: true }) title: string; // ... } export const PostSchema = SchemaFactory.createForClass(Post);By doing the above, we speed up queries, such as when we look for a post with a specific title, for example. We also speed up queries where we sort posts by the title alphabetically. Text indexes MongoDB also implements text indexes that support search queries on string content. To define a text index, we need to use the index() method. post.schema.ts import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Document, ObjectId } from 'mongoose'; import { Transform } from 'class-transformer'; export type PostDocument = Post & Document; @Schema() export class Post { @Transform(({ value }) => value.toString()) _id: ObjectId; @Prop() title: string; // ... } const PostSchema = SchemaFactory.createForClass(Post); PostSchema.index({ title: 'text' }); export { PostSchema };When we set up a text index, we can take advantage of the $text operator. It performs a text search on the content of the fields indexed with a text index. A collection can’t have more than one text index. Let’s implement a feature of searching through our posts by adding a new query parameter. post.controller.ts import { Controller, Get, Query, UseInterceptors, } from '@nestjs/common'; import PostsService from './posts.service'; import MongooseClassSerializerInterceptor from '../utils/mongooseClassSerializer.interceptor'; import { Post as PostModel } from './post.schema'; import { PaginationParams } from '../utils/paginationParams'; @Controller('posts') @UseInterceptors(MongooseClassSerializerInterceptor(PostModel)) export default class PostsController { constructor(private readonly postsService: PostsService) {} @Get() async getAllPosts( @Query() { skip, limit, startId }: PaginationParams, @Query('searchQuery') searchQuery: string, ) { return this.postsService.findAll(skip, limit, startId, searchQuery); } // ... }We also need to add the $text query to the service. post.service.ts import { FilterQuery, Model } from 'mongoose'; import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Post, PostDocument } from './post.schema'; @Injectable() class PostsService { constructor(@InjectModel(Post.name) private postModel: Model