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 { CustomersProfile } from './interfaces/customers-profile.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<CustomersProfile> {
const newCustomer = await new this.customerModel(createCustomerDto);
return newCustomer.save();
}
public async update(
customerId: string,
updateCustomerDto: UpdateCustomerDto,
): Promise<CustomersProfile> {
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;
}
}
CustomersProfile:
import { Document } from 'mongoose';
export interface CustomersProfile extends Document {
readonly firstName: string;
readonly lastName: string;
readonly email: string;
readonly phone: string;
readonly address: string;
readonly description: string;
}
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 nestjs-api-mongoose
Top comments (12)
Yuk! So we have to define a several schema, a several DTOs and a couple interfaces (separately) for each model?
This is sorta ugly :/
Same feeling, the title is "Simple example Api Rest " but the implementation feels pretty complex and repetitive; and the nest documentation is not so clear either.
Is this the Nest way to use mongoose ? Is there simpler alternatives ?
Things seems much simpler with Typeorm than with mongoose, but Typeorm says that it has only a "basic support of mongodb"
Hi Yann,
Everyone can use the methods and implementation they want, you are free to implement them however you like, the choice is yours. Then for the following questions you ask:
Is this the Nest way to use mongoose?
The implementation for using mongoose with Nest is this, I know maybe mongoose is a bit complex to understand.
Is there simpler alternatives ?
an alternative to mongoose?
Actually your examples are okay, i was just not used to Nest so some stuff seemed redundant at first glance, but it is not.
Mongoose schema is dedicated to database and might have nested properties, ref to other collections etc
CreateUserDto can be different from schema for various reasons
You created UpdateUserDto from CreateUserDto to avoid dupplicating code
This makes sense now
Hi Yann, don't worry, sometimes it can happen that you don't understand the code right away and you need some time to understand I've been through it too. Then in software development there will always be difficulties and understanding the code as it is written and why it was written that way.
When I first used Nest, I understood it in a flash, as if I had used it before, but I think it's because I know Angular from the first version and some things are very familiar to me.
However, if you have any doubts, write me without problems.
P.s. If you are having difficulty with Nest you can also use the Discord channel for support
If you find other ways of implementation you are free to implement them as you like, the choice is yours.
Hi Tony, Can you post a more complex example with a Model field ref to another Model and an Image upload
Hi ryanhex53,
Yes, of course I could, but I'm a little busy right now and I don't know if I can do it.
In practice, if I'm not mistaken, it should be an example similar to what I did but with the addition of the upload files?
I have other NestJS posts in mind but unfortunately I never have enough time to prepare them.
Hi ryanhex53,
Yes, of course I could, but I'm a little busy right now and I don't know if I can do it.
In practice, if I'm not mistaken, it should be an example similar to what I did but with the addition of the upload files?
I have other NestJS posts in mind but unfortunately I never have enough time to prepare them.
Great!
good tutorial
Thanks! I'm glad it was useful.