Nest is a framework for building efficient, scalable Node.js server-side applications. It builds upon Express.js and embraces TypeScript, Dependency Injection, and Modularity ( Features Angular developers should be familiar with). Also combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
To get more information on Nest Js , you can look up its documentation.
While on the other hand MongoDB is built on a scale-out architecture that has become popular with developers of all kinds for developing scalable applications with evolving data schemas.
As a document database, it is easy for developers to store structured or unstructured data. It uses a JSON-like format to store documents.
Installation
This installation guide will be based on Windows. You can look up Installation processes for other platforms by consulting the Nest.Js documentation.
Installing the Nest CLI
First, you need to make sure you have Node JS installed on your computer, just follow the instructions on https://nodejs.org/ to install Node.js and NPM on your computer.
After that open your terminal and execute this command to install the Nest Js CLI:
npm i-g @nestjs/cli
Once the installation is complete, the next thing to do is to create a nest project and this can be done by executing this command in your terminal:
nest new ProjectName
The above code creates a project with whatever name you choose to give the project
E.g
nest new test-project
The command creates a project folder named test-project which will have :
For a detailed dive into the Nest project folder structure, I'm working on a piece on NEST JS as a backend.
Now let's navigate to the SRC folder, for us is the most important folder in this project folder, inside of which we have :
- main.ts: This is the entry point of our Nest JS application.
- app.module.ts: This file contains the implementation of the application’s root module housing controllers and providers.
- app.controller.ts: This file contains the implementation of a basic NestJS controller with just one route.
- app.service.ts: This file contains a basic service implementation.
- app.controller.spec.ts: This is the testing file for our controller.
Creating a “Product” feature
Now, we are about to start the proper implementation of a simple Nest Js Crud operation with MongoDB.
Let's start by navigating to the SRC folder of our project folder structure and creating a PRODUCT folder, in this PRODUCT folder, we will be generating a product.module.ts,product.service.ts, and product.controller.ts file.
This can be done by executing this command in your terminal:
# ProductModule
nest g module Product
# ProductService
nest g service Product
# ProductController
nest g controller Product
This will generate features of the Product folder and it will also add the ProductService under the providers
array in the ProductModule and ProductController in the **controllers
** array.
Creating a Model
Now, we will create a product model, this model defines what our product will look like. We can do this by creating a new file in the PRODUCT folder and naming it product.model.ts.
Then we proceed to create a schema using the Mongoose package(You can look up Mongoose documentation for more information on Mongoose Package ). This can be done by installing the Mongoose Package in our nest Project, run this code in your terminal:
npm install --save mongoose @nestjs/mongoose
After the installation, we add the MongooseModule
into the imports array of theAppModule
. This is to let the application know where the MongoDB Atlas database is coming from. This article, assumes you have at least a basic understanding of how to set up your MongoDB Atlas database. However, if you aren't familiar with this, you can watch this tutorial by ACADEMIND on how to set up a MongoDB Atlas database:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forRoot('mongodb+srv://<UserName>:<Password>@<cluster>.ubhf3.mongodb.net<Databasename>?retryWrites=true&w=majority'),],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
After adding MongooseModule.forRoot()
in AppModule
, we can then proceed to define the Product Model and Schema, navigate into the product.model.ts file, and then we can define the product model this way:
import * as mongoose from 'mongoose'
export const ProductSchema = new mongoose.Schema({
title: String,
description: String,
price: Number,
})
export interface Product extends mongoose.Document {
id: string;
title: string;
description: string;
price: number;
}
First, we import all as mongoose from the mongoose package, then we export a constant named ProductSchema which will serve as a blueprint for our model, then we have a new mongooseSchema which is a constructor and we pass an object in the mongooseSchema where we define how the object should look like.
As you can see, the Product interface extends a mongoose. Document which is made possible by running this command in your terminal :
npm install --save-dev @types/mongoose
You might not need to extend the interface as a mongoose document because mongoose supports typescript starting from version 5.11.0, so if your version of mongoose is a current version, you might not need to install the @types/mongoose package.
Next, we import the MongooseModule and the ProductSchema in product.module.ts
import { Module } from '@nestjs/common';
import { ProductsController } from './products.controller';
import { ProductsService } from './products.service';
import { MongooseModule } from '@nestjs/mongoose';
import { ProductSchema } from './products.model';
@Module({
imports: [MongooseModule.forFeature([{name: 'Product', schema: ProductSchema}])],
controllers: [ProductsController],
providers: [ProductsService],
})
export class ProductsModule {}
Next, we import and inject the InjectModel from the @nestjs/mongoose into the product.service.ts, also we import the ProductModel from product.model.ts and Model from Mongoose
import { Injectable } from '@nestjs/common';
import { Product } from './products.model';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
@Injectable()
export class ProductsService {
products: Product[] = [];
constructor(@InjectModel('Product') private readonly productModel: Model<Product>){}
}
In the constructor we use @InjectModel(Product)
and pass in the name of the model productModel and we set it as a private property and gave it a type of Model
where we also pass a generic type of Product that we defined from the Product model from product.model.ts, giving us all the methods from Mongoose for perfoming CRUD operations for MongoDB.
Setting up CRUD Functionalities
Now we are about to proceed with setting up our crud methods. We will have the following methods to write up the implementation details: insertProducts()
, getAllProducts()
, getOneProduct(prodId:string)
, updateProduct(id: string, title: string, desc: string, price: number)
, delete(id: string)
and findProduct(id: string)
import { Injectable, NotFoundException } from '@nestjs/common';
import { Product } from './products.model';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
@Injectable()
export class ProductsService {
products: Product[] = [];
constructor(@InjectModel('Product') private readonly productModel: Model<Product>){}
async insertProduct(title:string, desc:string, price:number) {
const newProducts = new this.productModel({
title,
description: desc,
price,
});
const result = await newProducts.save();
return result;
}
async GetAllProducts() {
const products = await this.productModel.find().exec();
return products.map((prod) => ({
id: prod.id,
title: prod.title,
description: prod.description,
price: prod.price
}));
}
async GetProduct(prodId:string){
const product = await this.findProduct(prodId);
if (!product) {
throw new NotFoundException('Could not find product');
}
return product;
}
async updateProduct(prodId:string, title:string, desc:string, price:number) {
const updatedProduct = await this.findProduct(prodId);
if (title) {
updatedProduct.title = title;
}
if (desc){
updatedProduct.description = desc;
}
if (price) {
updatedProduct.price = price;
}
updatedProduct.save();
}
async deleteProduct(prodId:string){
const result = await this.productModel.deleteOne({_id:prodId}).exec();
if (result.n === 0) {
throw new NotFoundException('Could not find product');
}
}
private async findProduct(id:string): Promise<Product> {
let product;
try {
product = await this.productModel.findById(id);
}
catch(error) {
throw new NotFoundException('Could not find product');
}
if (!product) {
throw new NotFoundException('Could not find product');
}
return product;
}
}
Next, we navigate into the product.controller.ts file and inject the **ProductService
as our dependency for this `Controller`** class and also define all methods which will be used to access the functionality.
We will use the following names in the **Controller
** where addProduct()
is to add a product in the Database, getAllProduct()
to query a all Products in the Database, getSingleProduct()
is to query a single product in the Database, updateProduct()
to update an existing product based on a given ID, and lastly, removeProduct()
to delete a Product.
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
import { ProductsService } from './products.service';
@Controller('products')
export class ProductsController {
constructor( public productsService: ProductsService) {}
@Post()
async addProduct(
@Body('title') prodTitle: string,
@Body('description') prodDesc: string,
@Body('price') prodPrice: number
) {
const generatedId = await this.productsService.insertProduct(
prodTitle,
prodDesc,
prodPrice,
);
return {id: generatedId};
}
@Get()
async getAllProducts(){
const products = await this.productsService.GetAllProducts();
return products;
}
@Get(':id')
getSingleProduct(@Param('id') prodId:string){
return this.productsService.GetProduct(prodId);
}
@Patch(':id')
async updateProduct(
@Param('id') prodId:string,
@Body('title') prodTitle: string,
@Body('description') prodDesc: string,
@Body('price') prodPrice: number,
){
await this.productsService.updateProduct(
prodId,
prodTitle,
prodDesc,
prodPrice,
)
return null;
}
@Delete(':id')
removeProduct(@Param('id') prodId:string){
this.productsService.deleteProduct(prodId);
return null;
}
}
Testing
For testing, you can use any REST client of your choice, but this article uses POSTMAN(Postman is an application used for API testing. It is an HTTP client that tests HTTP requests, utilizing a graphical user interface, through which we obtain different types of responses that need to be subsequently validated).
Now open your REST client, let’s proceed to test the REST API we created so we can expect to add a product, update a product, delete a product, and read a product.
First, let's make a POST request to product
endpoint. but before you do that, make sure you have your cluster active and you have the server connected to the database. Execute this command in your terminal to start the server:
npm run start:dev
Now we have the server on and connected to the database, you should see something like this :
Next, navigate to the already opened POSTMAN and first, we add a product to the database by using the POST method, reading a product will return an empty array as we don’t have any product in the database.
Selecting the POST method option, while also indicating our endpoint URL which is a localhost:3000 as shown in the main.ts file that can be found in the src folder, we then add the target which is products, So we add a product in the database this way:
After filling in the params (title, description, and price) with values, click on the Send button to send the data into the database, you can see the generated result there, it returns a JSON object-generated Id which we have stored in id from the product.controller.ts file.
Next, we perform a read operation to query our database and read available data stored in the database. Remember, prior to us sending in data, the database was empty and hence would return an empty array when performing a GET method.
Now let's see what the GET method returns. Unlike the POST method, you don’t need to fill in any data, all you have to do is set the method in your POSTMAN to GET and the URL remains localhost:3000/products, then we click on the Send button to fetch all products from the database.
You can try adding more products to the database ◉‿◉.
Next, we fetch a single product from the database, this can be done by targeting the id of that product, still using the GET method, we query the database by adding the id to the URL.
Next, we update the price of the product in the database by using the PATCH method. WHY? well, unlike the PUT method that modifies the whole resources, PATCH does a partial update, targeting only the field you want to update without touching the other fields. The updated product method is set to return null, that’s why you can’t see a response there in the POSTMAN. How then do you know if the operation was executed successfully, you can check your database to see the changes effected or you can return
await this.productService.updateProduct(prodId,prodTitle,prodDesc,prodPrice) in the product.service.ts file.
And finally, we perform a delete operation by using the DELETE method. by targeting an id of a product, we can delete the product from the database.
And that is all for now😊
Conclusion
I have fallen in love with NEST JS as it allows me to create a quick REST API, with this I can also work with Angular on the frontend. NestJS is an awesome backend service for huge projects since the framework encourages the developer to use Domain-Driven Design.
I hope you enjoyed this tutorial, be sure to like, comment, and give your feedback on this, and also indicate what topic you would also like me to write on. Cheers! 😉