DEV Community

loading...

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

tony133 profile image Tony Updated on ・5 min read

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

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 NestJSApiMongoose

Discussion (4)

pic
Editor guide
Collapse
andrewnijmeh profile image
Andrew Nijmeh

good tutorial

Collapse
tony133 profile image
Tony Author

Thanks! I'm glad it was useful.

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
tony133 profile image
Tony Author

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