MongooseMap in Express and NestJS

Piyush Kacha - Mar 25 '23 - - Dev Community

MongooseMap is a special type of Map class in JavaScript that is used in Mongoose to create nested documents with custom keys. When using Mongoose Maps, it is important to remember that keys must always be strings in order for the document to be stored in MongoDB.

For example, in a userSchema, you can create a socialMediaHandles map that has values of strings by specifying the type as Map and the type of values using "of" property.

You can set key-value pairs for socialMediaHandles using .set() method or by setting the value directly. However, it is important to note that you must use .get() to retrieve the value of a key.

For instance, if you try to set a key that is not explicitly declared in the schema, such as "myspace", it will not get saved. Only keys that are specified in the map will be saved.

In summary, Mongoose Maps are a convenient way to create nested documents with custom keys in Mongoose, but it is important to remember to use strings as keys and to only set values using the .set() method or by setting the value directly for keys that are specified in the map.

Here's an example schema:

const postSchema = new Schema({
  title: {
    type: String,
    required: true
  },
  content: {
    type: String,
    required: true
  },
  metadata: {
    type: Map,
    of: String
  }
});

const Post = mongoose.model('Post', postSchema);
Enter fullscreen mode Exit fullscreen mode

In this schema, we have a "metadata" field that is a MongooseMap. This allows us to store arbitrary key/value pairs for each post.

Let's create a new post and add some metadata to it:

const post = new Post({
  title: 'My First Blog Post',
  content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
  metadata: {}
});

// Add metadata using the .set() method
post.metadata.set('author', 'John Doe');
post.metadata.set('published', 'true');
post.metadata.set('views', '123');

// Alternatively, you can set the metadata value directly
post.metadata.comments = '5';

// Get the value of a specific key using .get() method
console.log(post.metadata.get('author')); // 'John Doe'

// Save the post to the database
post.save();
Enter fullscreen mode Exit fullscreen mode

In this example, we create a new post and add some metadata to it using the .set() method and by setting the value directly. We can retrieve the value of a specific key using the .get() method.

When we save the post to the database, the metadata field will be stored as a MongooseMap with the specified key/value pairs. This allows us to create a flexible data model that can accommodate a wide range of user-defined metadata for each post.

Here's an example of how you can use Mongoose and Nest.js to create a blog post model with a metadata MongooseMap:

import { Schema, Prop, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type PostDocument = Post & Document;

@Schema()
export class Post {
  @Prop({ required: true })
  title: string;

  @Prop({ required: true })
  content: string;

  @Prop({ type: Map, of: String })
  metadata: Map<string, string>;
}

export const PostSchema = SchemaFactory.createForClass(Post);

Enter fullscreen mode Exit fullscreen mode

n this example, we define a Post class with a title, content, and metadata field. We use the @Prop decorator from the @nestjs/mongoose module to define the schema for each field.

For the metadata field, we specify the type as Map and the type of values as String using the of property. We also use the Map generic type to ensure that the keys are strings.

To use this model in a Nest.js controller, you can create a service and inject the PostModel:

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Post, PostDocument } from './post.model';

@Injectable()
export class PostService {
  constructor(@InjectModel(Post.name) private postModel: Model<PostDocument>) {}

  async createPost(post: Post): Promise<Post> {
    const createdPost = new this.postModel(post);
    return createdPost.save();
  }

  async getPostById(id: string): Promise<Post> {
    return this.postModel.findById(id).exec();
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, we inject the PostModel into the PostService constructor using the @InjectModel decorator. We can then use the Mongoose model methods to create and retrieve posts.

Here's an example of how you can use the PostService to create a new post and add metadata to it:

import { Controller, Post, Body, Get, Param } from '@nestjs/common';
import { PostService } from './post.service';
import { Post as PostModel } from './post.model';

@Controller('posts')
export class PostController {
  constructor(private postService: PostService) {}

  @Post()
  async createPost(@Body() post: PostModel): Promise<PostModel> {
    return this.postService.createPost(post);
  }

  @Get(':id')
  async getPostById(@Param('id') id: string): Promise<PostModel> {
    return this.postService.getPostById(id);
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, we define a PostController with a createPost method that accepts a PostModel object in the request body and a getPostById method that retrieves a post by ID.

We can use these endpoints to create a new post and add metadata to it:

// POST /posts
{
  "title": "My First Blog Post",
  "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
  "metadata": {
    "author": "John Doe",
    "published": "true",
    "views": "123",
    "comments": "5"
  }
}

Enter fullscreen mode Exit fullscreen mode
. . . .