Logo
Published on

NestJS and MongoDB: A Step-by-Step Integration Guide

Authors
  • Name
    Twitter

Introduction

Greetings, fellow developers! 🚀 Venturing into the vast universe of backend development, we often find ourselves at the crossroads of choosing the right tools. Today, I’m here to guide you through one such powerful combination: NestJS and MongoDB. We’ll embark on a journey to seamlessly integrate MongoDB into a NestJS application. Whether you’re a seasoned developer or just getting your feet wet, this guide promises clarity and actionable steps. So, grab your favorite beverage, and let’s dive right in!

Prerequisites

  • Basic understanding of TypeScript.
  • Node.js installed.
  • MongoDB running locally or a connection string to a cloud instance.
  • NestJS CLI installed. If not, run:
npm i -g @nestjs/cli

1. Setting Up a New NestJS Project

Kick things off by creating a new NestJS project:

nest new nest-mongodb

Navigate to your project:

cd nest-mongodb

2. Installing Dependencies

For MongoDB, we’ll use Mongoose, an elegant ODM (Object Document Mapper):

npm install mongoose

3. Configuring MongoDB in AppModule

Open app.module.ts and set up the MongooseModule:

import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";

@Module({
  imports: [MongooseModule.forRoot(process.env.MONGO_CONNECTION_STRING)],
  // ... other properties
})
export class AppModule {}

Note: Always use environment variables for sensitive data like connection strings. Consider using packages like _dotenv_ for this.

4. Creating the User Module

Generate a new resource for the user:

nest generate resource module/user

Now, let’s define our User entity:

// user.entity.ts
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Document } from "mongoose";
import { v4 as uuid } from "uuid";
import { Type } from "class-transformer";
import { Client } from "src/modules/client/entities/client.entity";

export type UserDocument = User & Document;

@Schema({
  toJSON: {
    getters: true,
    virtuals: true,
  },
  timestamps: true,
})
export class User {
  @Prop({
    type: String,
    unique: true,
    default: function genUUID() {
      return uuid();
    },
  })
  userId: string;

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

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

  @Prop({ required: true, unique: true })
  email: string;

  @Prop({ required: false })
  clientId?: string;

  @Type(() => Client)
  Client: Client;

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

export const UserSchema = SchemaFactory.createForClass(User);

UserSchema.virtual("Client", {
  ref: Client.name,
  localField: "clientId",
  foreignField: "clientId",
  justOne: true,
});

5. Creating the Client Module

Similarly, generate a resource for the client:

nest generate resource module/client

Define the Client entity:

// client.entity.ts
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Document } from "mongoose";
import { v4 as uuid } from "uuid";
import { Client } from "src/modules/user/entities/user.entity";

export type ClientDocument = Client & Document;

@Schema({
  toJSON: {
    getters: true,
    virtuals: true,
  },
  timestamps: true,
})
export class Client {
  @Prop({ required: true })
  name: string;

  @Prop({
    required: true,
    unique: true,
    default: function genUUID() {
      return uuid();
    },
  })
  clientId: string;
}

export const ClientSchema = SchemaFactory.createForClass(Client);

ClientSchema.virtual("Users", {
  ref: User.name,
  localField: "clientId",
  foreignField: "clientId",
  justOne: false,
});

6. Centralizing Mongoose Features

To avoid redundancy and make our codebase cleaner, let’s centralize our Mongoose features:

// src/db/for-feature.db.ts
import { User, UserSchema } from "src/modules/users/entities/user.entity";
import {
  Client,
  ClientSchema,
} from "src/modules/clients/entities/client.entity";

export default [
  { name: User.name, schema: UserSchema },
  { name: Client.name, schema: ClientSchema },
];

7. Integrating Mongoose Features in Modules

For both client.module.ts and user.module.ts, import the centralized Mongoose features:

// client.module.ts
import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";
import forFeatureDb from "src/db/for-feature.db";
import { ClientController } from "./client.controller";
import { ClientService } from "./client.service";

@Module({
  controllers: [ClientController],
  providers: [ClientService],
  imports: [MongooseModule.forFeature(forFeatureDb)],
  exports: [ClientService],
})
export class ClientModule {}
// user.module.ts
import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";
import forFeatureDb from "src/db/for-feature.db";
import { UserController } from "./user.controller";
import { UserService } from "./user.service";

@Module({
  controllers: [UserController],
  providers: [UsersService],
  imports: [MongooseModule.forFeature(forFeatureDb)],
  exports: [UserService],
})
export class UserModule {}

8. Building the User and Client Services

In the service files, we’ll handle our CRUD operations. Here’s how you can set up the UserService:

// user.service.ts
import { Injectable } from  '@nestjs/common';
import { CreateUserDto } from  './dto/create-user.dto';
import { User, UserDocument } from  './entities/user.entity';
import { InjectModel } from  '@nestjs/mongoose';
import { Model } from  'mongoose';

@Injectable()
export  class  UserService {
constructor(@InjectModel(User.name) private readonly userModel: Model<UserDocument>) {}

create(createUserDto: CreateUserDto) {
return  this.userModel.create({ ...createUserDto });
}

// ... other CRUD operations
}
```Similarly, set up the  `ClientService`.

## **9. Setting Up Controllers**

Controllers handle incoming HTTP requests. For the  `UserController`:

```ts
// user.controller.ts
import { Controller, Post, Body, UseGuards } from  '@nestjs/common';
import { UserService } from  './user.service';
import { CreateUserDto } from  './dto/create-user.dto';


@Controller('user')
export  class  UserController {
constructor(private readonly userService: UserService) {}

@Post()
create(@Body() createUserDto: CreateUserDto) {
return  this.userService.create(createUserDto);
}

// ... other endpoints
}

Repeat the process for the ClientController.

10. Relationships in MongoDB

The virtuals in our schemas allow us to create relationships between collections. This isn’t native to MongoDB but is a powerful feature provided by Mongoose. In our example, a User can be linked to a Client, and vice versa.

For example, to retrieve data related to a User and their associated Client, you can use the following code:

this.userModel.find().populate('Client').lean();

This code utilizes the power of Mongoose’s virtuals to establish connections between collections.

For more information about virtuals, you can refer to https://mongoosejs.com/docs/tutorials/virtuals.html

Conclusion

Congratulations! You’ve just set up a NestJS application integrated with MongoDB. This guide aimed to provide a beginner-friendly approach, but always remember that the journey of mastering any technology requires continuous learning and practice. Dive deeper and explore more features.

For more insights and discussions, feel free to visit my Medium Blog.

Happy coding! 🚀