MongooseMap in Express and NestJS
Tags: mongodb, nestjs, typescript, tutorial
Published: 2023-03-25 13:53:45

**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:

```javascript
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);
```

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:

```javascript
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();
```
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`:

```typescript
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);

``` 
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:

```typescript
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();
  }
}
```
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:
```typescript
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);
  }
}
```
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:

```javascript
// 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"
  }
}

```