Building a CRUD API with NESTJS and MongoDB

Building a CRUD API with NESTJS and MongoDB

Getting started with NESTJS

NestJS is a sever side framework based on node.js used to build efficient and scalable server-side application .It uses Javascript and comes along with build in Typescript support

NestJS uses express.js as a default HTTP server frameworks and can be optionally configured to use Fastify

Installation

To create a project from CLI install the nest package globally

$ npm i -g @nestjs/cli

Creating a new project

$ nest new project-name

After scaffolding your project directory should more or less look similar to the below image

Alt text of image

Starting point of the application

Main.ts is the staring point of the application which used port 3000 in mycase to listen for api endpoints

image.png

The main.ts file will bootstrap the application .All the dependency will be pointed in App module and that module will be imported to the main.ts file

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

bootstrap();

Before divining further we need to know about controller, provider and module in NestJS

Module

It's a good practice to separate each functionality into a module . For example a user module all the functionality like creating / updating /deleting the user will go into this module . Like this we can separate each module

The below command will will generate a module through CLI

nest generate module moduleName

Controller and Provider(Service)

The controller is the point where we write our Api methods like get,put ,post api methods. Below cmd will create a controller from Nest CLI.

nest g controller directory/controller_name

Inorder to delegate the task of database logic we separately write a service to handle the interaction with DB . Below cmd will create a service from Nest CLI.

nest g service directory/service_name

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from '@nestjs/mongoose';
import { LeagueModule } from './league/league.module';

@Module({
imports: [
MongooseModule.forRoot(
  'mongodb+srv://practice:Mongo7$@practice.a2eyo.mongodb.net/League?retryWrites=true&w=majority',
),
LeagueModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

In the above image you can see we have imports , controller , providers We have configure our MongoDB connection in imports .The @nestjs/mongoose package is a NestJS wrapper for mongoDB it work's just like mongoose.connect() express

npm i @nestjs/mongoose mongoose

This below is the schema for the League Model . The Schema is created using the nestjs/mongoose but you can create it using the mongoose ORM as well.

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
import * as mongoose from 'mongoose';

export type LeagueDocument = HydratedDocument<LeagueModel>;

const title = new mongoose.Schema({
UCL: { type: Number, require: true },
League: { type: Number, require: true },
});

@Schema({ timestamps: true })
export class LeagueModel {
@Prop({ required: true })
Team: string;

@Prop({ type: title, required: true })
title: typeof title;

@Prop({ required: true })
League: string;

@Prop({ required: true })
marqueePlayer: string;
}

export const LeagueSchema = SchemaFactory.createForClass(LeagueModel);

Explanation about my crud App

Method: Get() ,Post(:id),Put(:id),Delete(:id)
Endpoint:http://localhost:3000/league
Body:
{
    "Team": "Real Madrid",
    "title": {
        "UCL": 14,
        "_id": "637a7387c07ba7d9309b386a"
    },
    "marqueePlayer": "Karim Benzema",
},

This will be JSON data for our endpoint to create the record in the DB .

Wiring up

One import factor to note is the wiring of all controller ,Schema ,Service in League Model.

The league model imports the MongoDB model for this module . The LeagueSchema is being fed to the module to make use of the model

Module is also provided with the controller and provider(the service). Here the module doesn't know about the controller and service to use .We have include the appropriate controller and service in the module


image.png

LeagueModel.ts
import { Module } from '@nestjs/common';      
import { LeagueControllerController } from './controller/league-controller/league-controller.controller';
import { LeagueServiceService } from './services/league-service/league-service.service';
import { MongooseModule } from '@nestjs/mongoose';
import { LeagueSchema } from '../MongoSchema/League';

@Module({
  imports: [
    MongooseModule.forFeature([{ name: 'LeagueSchema', schema: LeagueSchema }]),
  ],
  controllers: [LeagueControllerController],
  providers: [LeagueServiceService],
})
export class LeagueModule {}

The controller will be the point to get the request object and there after will delegate it to service to handle the DB logic and if everything goes well the controllers will return result with apropriate status code.

league.controller.ts
import {
  Controller,
  Post,
  Body,
  HttpStatus,
  HttpException,
  Delete,
  Param,
  ParseIntPipe,
  Put,
  Get,
} from '@nestjs/common';
import { LeagueServiceService } from '../../services/league-service/league-service.service';
import { League } from '../../Dto/league.dto';
import { ValidationPipe } from '../../validation/validation.pipe';

@Controller('league')
export class LeagueControllerController {
  constructor(private readonly LeagueServiceService: LeagueServiceService) {}
  @Get()
  async querydata() {
    try {
      const data = await this.LeagueServiceService.queryall();

      if (data) {
        return data;
      } else {
        throw new HttpException(
          { message: 'Something went wrong! failed to fetch' },
          HttpStatus.FAILED_DEPENDENCY,
        );
      }
    } catch (error) {
      return { error };
    }
  }
  @Post('/create')
  async create(@Body(new ValidationPipe()) league: League) {
    try {
      const leaguerespose = await this.LeagueServiceService.create(league);
      if (leaguerespose) {
        return {
          messgae: 'created succuessfully',
          status: HttpStatus.CREATED,
        };
      } else {
        throw new HttpException('Creation failed', HttpStatus.BAD_REQUEST);
      }
    } catch (error) {
      return { error: error };
    }
  }

  @Delete('remove/:id')
  async removeTeam(@Param('id') id: string) {
    try {
      console.log(id);
      const removedStatus = await this.LeagueServiceService.remove(id);
      //console.log(removedStatus);

      if (removedStatus) {
        return {
          message: 'Delete successfully',
          status: HttpStatus.ACCEPTED,
        };
      } else {
        throw new HttpException(
          { message: 'Failed to delete' },
          HttpStatus.BAD_REQUEST,
        );
      }
    } catch (error) {
      return new HttpException({ error }, HttpStatus.BAD_REQUEST);
    }
  }

  @Put('/edit/:id')
  async Modify(
    @Body(new ValidationPipe()) league: League,
    @Param('id') id: string,
  ) {
    try {
      console.log(league, id);
      const modify = await this.LeagueServiceService.modify(id, league);

      if (modify) {
        return {
          message: 'Updates successfully',
          status: HttpStatus.ACCEPTED,
        };
      }
    } catch (error) {
      return { error: error };
    }
  }
}

Service(Provider) This acts as a service that will handle to Database logic to write/put to database

  import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
  import { League } from '../../Dto/league.dto';
  import { Connection, Model, sanitizeFilter } from 'mongoose';
  import { InjectConnection, InjectModel } from '@nestjs/mongoose';
  import { stat } from 'fs';

  @Injectable()
  export class LeagueServiceService {
    constructor(
      @InjectModel('LeagueSchema') private readonly LeagueModel: Model<League>,
    ) {}

    async queryall() {
      try {
        const data = await this.LeagueModel.find();
        return data;
      } catch (error) {
        throw new HttpException(
          { message: 'failed to query data' },
          HttpStatus.EXPECTATION_FAILED,
        );
      }
    }


    async create(league: League): Promise<League> {
      try {
        const createLeague = new this.LeagueModel(league);
        return await createLeague.save();
      } catch (error) {
        throw new Error();
      }
    }

    ///Get method

    async remove(id: string) {
      try {
        const status = await this.LeagueModel.findByIdAndDelete(id);
        // console.log(status + 'Jii');
        return status;
      } catch (error) {
        throw new Error(error);
      }
    }

    async modify(id: string, body: League) {
      try {
        const data = await this.LeagueModel.findById(id);
        const updated = await this.LeagueModel.updateOne({ _id: id }, body);
        return updated;
      } catch (error) {
        throw new HttpException(
          { message: 'Failed to Update' },
          HttpStatus.NOT_MODIFIED,
        );
      }
    }
  }

Dependency Injection

Dependency injection is basically providing the objects that an object needs (its dependencies) instead of having it construct them itself. It's a very useful technique for testing, since it allows dependencies to be mocked or stubbed out.

NestJS runtime has IOC container where we will be delegating our dependencies and through constructor of class we will use these instance instead of creating a instance with new Keyword.

For more on dependency injection go through the below

https://www.freecodecamp.org/news/a-quick-intro-to-dependency-injection-what-it-is-and-when-to-use-it-7578c84fa88f/

Did you find this article valuable?

Support Mohammed Sharooque by becoming a sponsor. Any amount is appreciated!