Exploring a NestJS Application with MongoDB Using Mongoose: A Beginner’s Guide (P1)
This guide provides a simple overview of setting up a NestJS application with MongoDB using the Mongoose module. It includes creating modules, services, schemas, and integrating Mongoose for database operations, illustrated with diagrams for clarity. If you prefer the coding version, you can explore the code here: https://github.com/kelvin-bz/nestjs-mongoose
The application will be very simple to create and list the users
Overview
Code Structure
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── config
│ └── database.config.ts
├── main.ts
└── users
├── dto
│ └── create-user.dto.ts
├── schemas
│ └── user.schema.ts
├── user.module.ts
├── users.controller.ts
└── users.service.ts
Config Directory
- config/database.config.ts: Configuration file for database settings, including MongoDB connection details.
Users Directory
This directory contains all the components related to the user functionality, including DTOs, schemas, modules, controllers, and services.
- dto/create-user.dto.ts: Defines the data transfer object (DTO) for creating a user. This specifies the shape of the data sent to the application.
- schemas/user.schema.ts: Defines the Mongoose schema for the User model. This schema maps to a MongoDB collection and defines the structure of the documents within that collection.
- user.module.ts: The module that imports MongooseModule to define and connect the
User
schema. This module organizes the users' related components and integrates Mongoose for database operations - users.controller.ts: Handles incoming HTTP requests related to user operations, such as creating and retrieving users. It uses
UsersService
to perform these operations. - users.service.ts: Contains the business logic for user operations. It interacts with the
UserModel
to perform database operations such as creating and retrieving users.
AppModule with Dynamic Mongoose Module Configuration
// src/app.module.ts
// .. import
@Module({
imports: [
ConfigModule.forRoot({
load: [databaseConfig],
envFilePath: '.env',
isGlobal: true,
}),
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
return {
uri: configService.get(CONFIG_DATABASE).users.uri,
};
},
inject: [ConfigService],
}),
UsersModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
// src/config/database.config.ts
import { registerAs } from '@nestjs/config';
export const CONFIG_DATABASE = 'database';
export default registerAs(CONFIG_DATABASE, () => ({
users: {
uri: process.env.DATABASE_URL,
},
}));
The AppModule
is the root module of your NestJS application. It imports and configures several modules:
ConfigModule
: Loads configuration from .env and is globally available.MongooseModule
: Asynchronously sets up the MongoDB connection using configuration from ConfigModule. You place this in your rootAppModule
, ensuring the connection is established once and is available throughout the entire app. UsingforRootAsync
, you can dynamically configure this connection at runtime, pulling in environment variables or other configuration settings as needed.UsersModule
: Manages user-related functionality
With Mongoose, the dynamic module setup MongooseModule.forRootAsync
enables the database connection to be configured based on environment variables or external inputs. This approach provides enhanced scalability and adaptability, ensuring that the application can handle various deployment scenarios. It allows the module to be customized dynamically, improving maintainability and flexibility.
A dynamic module in NestJS allows for asynchronous configuration and dependency injection at runtime, making it highly flexible and adaptable. It enables the module to be configured dynamically based on runtime conditions or external inputs, such as environment variables or configuration files. This is particularly useful for setting up complex dependencies like database connections. Dynamic modules enhance scalability and maintainability by allowing for more sophisticated initialization logic. https://docs.nestjs.com/fundamentals/dynamic-modules
User Module
The Users module is where all the user-related functionality is encapsulated. It includes the controller, service, and schema definitions.
// import ...
@Module({
imports: [
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
MongooseModule.forFeature
The MongooseModule.forFeature
is where you set up the specifics for each feature module, like UsersModule
. This is where you register your Mongoose schemas and create models. You use it within each feature module to define and connect the necessary models, ensuring each module has access to the database structures it needs to function.
User Controller — User Service — User Schema
Users controller handles incoming requests related to users, such as creating, retrieving, and deleting user records.
// src/users/dto/create-user.dto.ts
export class CreateUserDto {
readonly name: string;
readonly age: number;
}
// src/users/users.controller.ts
// import ...
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
async create(@Body() createUserDto: CreateUserDto) {
await this.usersService.create(createUserDto);
}
@Get()
async findAll(): Promise<User[]> {
return this.usersService.findAll();
}
}
The Users service handles the business logic related to users, including creating and retrieving user records from the MongoDB database.
// src/users/users.service.ts
// import ...
@Injectable()
export class UsersService {
constructor(@InjectModel(User.name) private userModel: Model<User>) {}
async create(createUserDto: CreateUserDto): Promise<User> {
const createdUser = new this.userModel(createUserDto);
return createdUser.save();
}
async findAll(): Promise<User[]> {
return this.userModel.find().exec();
}
}
The User schema defines the structure of the user documents stored in the MongoDB database.
// src/users/schemas/user.schema.ts
// import ...
export type UserDocument = HydratedDocument<User>;
@Schema()
export class User {
@Prop()
name: string;
@Prop()
age: number;
}
export const UserSchema = SchemaFactory.createForClass(User);
Testing the User Module
Create User
To create a new user, send a POST request to the endpoint below with the user details in the request body.
POST http://localhost:3000/users
Content-Type: application/json
{
"name": "John Doe",
"age": 30
}
Get All Users
To retrieve all users, send a GET request to the endpoint below.
GET http://localhost:3000/users
Conclusion
This guide illustrates how to set up a NestJS application with Mongoose to manage user data efficiently. By using MongooseModule.forRoot
for connecting to MongoDB and MongooseModule.forFeature
for registering schemas, you create a well-structured and maintainable system. The clear relationships between the User Controller, Service, Schema, and DTO ensure seamless data handling, providing a solid foundation for your application's user management needs.
Read more articles about NestJS : https://kelvinbz.medium.com/list/nestjs-eafb7de3e562
Read more articles about AWS: https://kelvinbz.medium.com/list/aws-32c5d9525c7d