In this post I will give you a simple example of how to use NestJS😻 with mongoose.
For those unfamiliar with or unfamiliar with NestJS, it's a Node.js TypeScript framework that helps you build efficient and scalable enterprise-grade Node.js applications.
While Mongoose is an Object Document Mapper (ODM). This means that Mongoose allows you to define objects with a strongly typed schema mapped to a MongoDB document.
One of the most vital concepts in MongoDB is the idea of ​​"data models".
These templates are responsible for creating, reading and deleting "documents" from the Mongo database.
If you come from an SQL background, one thing to remember about Mongo databases is that these documents are stored in "collections" and not in "tables".
Well now after this short intro let's get started!
So let's get started by creating the NestJS app
Open Terminal and install CLI for NestJS, if you have already installed it, skip this step.
$ npm i -g @nestjs/cli
Then create a NestJS project.
$ nest new nestj-api-mongoose
$ cd nestj-api-mongoose
// start the application
$ npm run start:dev
Open the browser on localhost:3000 to verify that hello world is displayed.
then we create a docker-compose.yml file for create service MongoDB
version: "3"
services:
mongodb:
image: mongo:latest
environment:
- MONGODB_DATABASE="nest"
ports:
- 27017:27017
for those who do not know what docker is I leave you the link here for more information: https://www.docker.com/get-started
There are many different ways to integrate Nest with databases, and all of them depend on personal preferences or project needs.
As I said we will use the most popular MongoDB object modeling tool called Mongoose.
If you’re having any issues setting up MongooseModule here - make sure that Docker is running with
docker compose up
Also make sure the db name inside your MongooseModule.forRoot matches what you have in your docker-compose file.
Install mongoose dependencies and devDependencies
$ npm i mongoose @nestjs/mongoose
$ npm i -D @types/mongoose
Setup MongooseModule in AppModule
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost:27017/nest'),
],
})
export class AppModule {}
Create a Mongoose Model:
import { Schema, Prop, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';
@Schema()
export class Customer extends Document {
@Prop()
firstName: string;
@Prop({ unique: true })
lastName: string;
@Prop({ unique: true })
email: string;
@Prop()
phone: string;
@Prop()
address: string;
@Prop()
description: string;
}
export const CustomerSchema = SchemaFactory.createForClass(Customer);
Create Dto(Data Transfer Objects) class for create customer
import { MaxLength, IsNotEmpty, IsEmail, IsString } from 'class-validator';
export class CreateCustomerDto {
@IsString()
@MaxLength(30)
@IsNotEmpty()
readonly firstName: string;
@IsString()
@MaxLength(30)
@IsNotEmpty()
readonly lastName: string;
@IsString()
@IsEmail()
@IsNotEmpty()
readonly email: string;
@IsString()
@MaxLength(30)
@IsNotEmpty()
readonly phone: string;
@IsString()
@MaxLength(40)
@IsNotEmpty()
readonly address: string;
@IsString()
@MaxLength(50)
readonly description: string;
}
Remember to install this package before creating the dto class for the update.
$ npm i @nestjs/mapped-types
Well now to update the customer data we extend the CreateCustomerDto class:
import { PartialType } from '@nestjs/mapped-types';
import { CreateCustomerDto } from './create-customer.dto';
export class UpdateCustomerDto extends PartialType(CreateCustomerDto) {}
we also create a dto class for the paginator
import { IsOptional, IsPositive } from 'class-validator';
export class PaginationQueryDto {
@IsOptional()
@IsPositive()
limit: number;
@IsOptional()
@IsPositive()
offset: number;
}
Well, now we will create a simple service and controller in a module, let's say the application will do something with customers and we want CustomersModule which will contain the customer domain objects, customer services and customer controllers.
nest g module customers
nest g service customers
nest g controller customers
You should now have a customers folder with CustomersModule, CustomersService and CustomersController inside.
our CustomersModule file should look like this:
import { Module } from '@nestjs/common';
import { CustomersService } from './customers.service';
import { CustomersController } from './customers.controller';
import { MongooseModule } from '@nestjs/mongoose';
import { CustomerSchema, Customer } from './schemas/customer.schema';
@Module({
imports: [
MongooseModule.forFeature([
{ name: Customer.name, schema: CustomerSchema },
]),
],
providers: [CustomersService],
controllers: [CustomersController],
})
export class CustomersModule {}
CustomersService:
import { Injectable, NotFoundException } from '@nestjs/common';
import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { ICustomer } from './interfaces/customer.interface';
import { CreateCustomerDto, UpdateCustomerDto } from './dto';
import { Customer } from './schemas/customer.schema';
import { PaginationQueryDto } from '../common/dto/pagination-query.dto';
@Injectable()
export class CustomersService {
constructor(
@InjectModel(Customer.name) private readonly customerModel: Model<Customer>,
) {}
public async findAll(
paginationQuery: PaginationQueryDto,
): Promise<Customer[]> {
const { limit, offset } = paginationQuery;
return await this.customerModel
.find()
.skip(offset)
.limit(limit)
.exec();
}
public async findOne(customerId: string): Promise<Customer> {
const customer = await this.customerModel
.findById({ _id: customerId })
.exec();
if (!customer) {
throw new NotFoundException(`Customer #${customerId} not found`);
}
return customer;
}
public async create(
createCustomerDto: CreateCustomerDto,
): Promise<ICustomer> {
const newCustomer = await new this.customerModel(createCustomerDto);
return newCustomer.save();
}
public async update(
customerId: string,
updateCustomerDto: UpdateCustomerDto,
): Promise<ICustomer> {
const existingCustomer = await this.customerModel.findByIdAndUpdate(
{ _id: customerId },
updateCustomerDto,
);
if (!existingCustomer) {
throw new NotFoundException(`Customer #${customerId} not found`);
}
return existingCustomer;
}
public async remove(customerId: string): Promise<any> {
const deletedCustomer = await this.customerModel.findByIdAndRemove(
customerId,
);
return deletedCustomer;
}
}
CustomersController:
import {
Controller,
Get,
Res,
HttpStatus,
Post,
Body,
Put,
NotFoundException,
Delete,
Param,
Query,
} from '@nestjs/common';
import { CustomersService } from './customers.service';
import { CreateCustomerDto, UpdateCustomerDto } from './dto';
import { PaginationQueryDto } from '../common/dto/pagination-query.dto';
@Controller('api/customers')
export class CustomersController {
constructor(private customersService: CustomersService) {}
@Get()
public async getAllCustomer(
@Res() res,
@Query() paginationQuery: PaginationQueryDto,
) {
const customers = await this.customersService.findAll(paginationQuery);
return res.status(HttpStatus.OK).json(customers);
}
@Get('/:id')
public async getCustomer(@Res() res, @Param('id') customerId: string) {
const customer = await this.customersService.findOne(customerId);
if (!customer) {
throw new NotFoundException('Customer does not exist!');
}
return res.status(HttpStatus.OK).json(customer);
}
@Post()
public async addCustomer(
@Res() res,
@Body() createCustomerDto: CreateCustomerDto,
) {
try {
const customer = await this.customersService.create(createCustomerDto);
return res.status(HttpStatus.OK).json({
message: 'Customer has been created successfully',
customer,
});
} catch (err) {
return res.status(HttpStatus.BAD_REQUEST).json({
message: 'Error: Customer not created!',
status: 400,
});
}
}
@Put('/:id')
public async updateCustomer(
@Res() res,
@Param('id') customerId: string,
@Body() updateCustomerDto: UpdateCustomerDto,
) {
try {
const customer = await this.customersService.update(
customerId,
updateCustomerDto,
);
if (!customer) {
throw new NotFoundException('Customer does not exist!');
}
return res.status(HttpStatus.OK).json({
message: 'Customer has been successfully updated',
customer,
});
} catch (err) {
return res.status(HttpStatus.BAD_REQUEST).json({
message: 'Error: Customer not updated!',
status: 400,
});
}
}
@Delete('/:id')
public async deleteCustomer(@Res() res, @Param('id') customerId: string) {
if (!customerId) {
throw new NotFoundException('Customer ID does not exist');
}
const customer = await this.customersService.remove(customerId);
if (!customer) {
throw new NotFoundException('Customer does not exist');
}
return res.status(HttpStatus.OK).json({
message: 'Customer has been deleted',
customer,
});
}
}
well now we should have our API tested if everything works perfectly this commands from curl or whatever you prefer to use.
$ curl -H 'content-type: application/json' -v -X GET http://127.0.0.1:3000/api/customers
$ curl -H 'content-type: application/json' -v -X GET http://127.0.0.1:3000/api/customers/:id
$ curl -H 'content-type: application/json' -v -X POST -d '{"firstName": "firstName #1", "lastName": "lastName #1", "email": "example@nest.it", "phone": "1234567890", "address": "street 1","description": "Lorem ipsum"}' http://127.0.0.1:3000/api/customers
$ curl -H 'content-type: application/json' -v -X PUT -d '{"firstName": "firstName #1", "lastName": "lastName #1", "email": "example@nest.it", "phone": "1234567890", "address": "street 1","description": "Lorem ipsum"}' http://127.0.0.1:3000/api/customers/:id
$ curl -H 'content-type: application/json' -v -X DELETE http://127.0.0.1:3000/api/customers/:id
This is all now you can try to experiment by creating an api organizations and embedding data or referring to customers but it mainly depends on how you will query and update your data or I did it via reference. So when you create a customer you "attach" them to their organization via their organization ID
😀 For anything write me in the comments 😉
I attach the repo with all the code NestJSApiMongoose
Discussion (4)
good tutorial
Thanks! I'm glad it was useful.
Yuk! So we have to define a several schema, a several DTOs and a couple interfaces (separately) for each model?
This is sorta ugly :/
If you find other ways of implementation you are free to implement them as you like, the choice is yours.