DEV Community

Antonio Tripodi
Antonio Tripodi

Posted on • Updated on

Simple example Api Rest with NestJS 7.x/8.x/9.x and Mongoose 😻

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
Enter fullscreen mode Exit fullscreen mode

Then create a NestJS project.

$ nest new nestj-api-mongoose
$ cd nestj-api-mongoose
// start the application
$ npm run start:dev
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Setup MongooseModule in AppModule

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost:27017/nest'),
  ],
})
export class AppModule {}

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

Remember to install this package before creating the dto class for the update.

$ npm i @nestjs/mapped-types
Enter fullscreen mode Exit fullscreen mode

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) {}

Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 {}

Enter fullscreen mode Exit fullscreen mode

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;
  }
}

Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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,
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

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 

Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
lowpolybrain profile image
Ilia Andrienko

Yuk! So we have to define a several schema, a several DTOs and a couple interfaces (separately) for each model?
This is sorta ugly :/

Collapse
 
yannyinnn profile image
Yann • Edited

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"

Collapse
 
tony133 profile image
Antonio Tripodi

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?

Thread Thread
 
yannyinnn profile image
Yann

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

Thread Thread
 
tony133 profile image
Antonio Tripodi

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

Collapse
 
tony133 profile image
Antonio Tripodi

If you find other ways of implementation you are free to implement them as you like, the choice is yours.

Collapse
 
ryanhex53 profile image
ryanhex53

Hi Tony, Can you post a more complex example with a Model field ref to another Model and an Image upload

Collapse
 
tony133 profile image
Antonio Tripodi

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.

Collapse
 
tony133 profile image
Antonio Tripodi

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.

Collapse
 
sebekd profile image
Sebastian Denis

Great!

Collapse
 
andrew_dev profile image
andrew_dev

good tutorial

Collapse
 
tony133 profile image
Antonio Tripodi

Thanks! I'm glad it was useful.