Getting Started with NestJs: CRUD Operations
Published: 2024-05-09 09:04:51

NestJS is a powerful framework for building efficient, scalable Node.js server-side applications. In this beginner-friendly guide, we'll walk through the creation of a simple NestJS project to help you understand its fundamental concepts

## Prerequisites

Before diving into NestJS, ensure you have Node.js and npm (Node Package Manager) installed on your system. You can download and install them from the official Node.js website.

**Setting Up Your NestJS Project**
First, let's create a new NestJS project. Open your terminal and run the following commands:
```
npm install -g @nestjs/cli
nest new my-nest-project
cd my-nest-project
```
This will set up a new NestJS project with the necessary dependencies and files.

## Exploring the Project Structure

Once your project is created, you'll find several files and folders. Let's go through them one by one:

1. main.ts: This file serves as the entry point to your NestJS application. It initializes the NestJS application and starts the serve.
 - Explanation: This file initializes the NestJS application by creating an instance of the AppModule.
 - Why it's important: Understanding the entry point of the application helps you grasp how NestJS bootstraps and starts your server.
 - dotenv.config(): This line loads environment variables from a .env file, allowing you to store sensitive information securely.

2. app.module.ts: The root module of your application. It imports other modules, controllers, and services and defines the main configuration of your app.

3. app.controller.ts: Controllers are responsible for handling incoming requests and returning responses. They are bound to specific routes and define endpoint logic.
 - Explanation: This controller defines a single route (GET /) that returns a "Hello World!" message.
 - Why it's important: Controllers handle incoming HTTP requests and define the endpoints of your application. They provide a clean separation of concerns by delegating actual logic to services.
 - Dependency Injection: Notice how the AppService is injected into the controller's constructor. This allows us to use the methods defined in AppService within the controller.

4. app.service.ts: Services contain the business logic of your application. They are injected into controllers to handle data processing and manipulation.
 - Explanation: The AppService class contains a single method getHello which returns a "Hello World!" message.
 - Why it's important: Services encapsulate business logic and data manipulation tasks. They are reusable across multiple controllers and promote code maintainability.

##  Running Your NestJS Application

```
npm run start
```
This will start the server, and you should see the message "Hello World!" when you navigate to http://localhost:3000 in your browser.

## Exploring the Project Structure

Once your project is created, you'll find several files and folders. Let's go through them one by one:

main.ts

```
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as dotenv from 'dotenv';

async function bootstrap() {
  dotenv.config();
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();
```
 - Explanation: This file is the entry point of the NestJS application. It initializes the NestJS application, loads environment variables, and starts the server.
 - Why it's important: Understanding the entry point helps you understand how NestJS bootstraps and starts the server.
 - dotenv.config(): This line loads environment variables from a .env file, allowing you to store sensitive information securely.

app.module.ts

```
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ProductsModule,
    ConfigModule.forRoot(),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule], 
      useFactory: async (configService: ConfigService) => ({
        type: 'postgres',
        host: configService.get('DB_HOST'),
        port: configService.get('DB_PORT'),
        username: configService.get('DB_USERNAME'),
        password: configService.get('DB_PASSWORD'),
        database: configService.get('DB_DATABASE'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: true,
      }),
      inject: [ConfigService],
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```
 - Explanation: This file defines the root module of the NestJS application. It imports other modules, controllers, and services and configures the application.
 - Why it's important: The root module is the core of your application. It orchestrates the various components and sets up configurations.

app.controller.ts

```
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
```
 - Explanation: This controller defines a single route (GET /) that returns a "Hello World!" message.
 - Why it's important: Controllers handle incoming HTTP requests and define the endpoints of your application.
 - Dependency Injection: Notice how the AppService is injected into the controller's constructor. This allows us to use the methods defined in AppService within the controller.

app.service.ts

```
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}
```
 - Explanation: The AppService class contains a single method getHello which returns a "Hello World!" message.
 - Why it's important: Services encapsulate business logic and data manipulation tasks. They are reusable across multiple controllers and promote code maintainability.

## CRUD Operations

Products: This folder contains modules, controllers, services, and entities related to managing products. It demonstrates how to organize code into separate modules for better maintainability. Let's go through them one by one:

products/product.entity.ts

```
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class ProductEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  title: string;

  @Column()
  description: string;

  @Column('decimal', { precision: 10, scale: 2 })
  price: number;
}
```
 - Explanation: This file defines the ProductEntity class, which represents a product entity in the database. It uses the TypeORM library for Object-Relational Mapping (ORM).
 - Why it's important: Entities map to database tables and represent the structure of the data stored in the database. They help in defining the schema and performing CRUD operations.

products/product.model.ts

```
export class Product {
    constructor (
        public id: string, 
        public title: string, 
        public description: string, 
        public price: number
        ) {
    };
}
```
 - Explanation: This file defines the Product class, which represents a product object in the application. It's a simple TypeScript class with properties corresponding to a product's attributes.
 - Why it's important: Models represent the data transferred between different parts of the application, such as between the controller and the service. They help maintain a consistent data structure throughout the application.

products/product.controller.ts

```
import { Body, Controller, Delete, Get, NotFoundException, Param, Patch, Post } from "@nestjs/common";
import { ProductsService } from "./products.service";

@Controller('products')
export class ProductsController {
    constructor(private readonly productsService: ProductsService) {}

    @Post()
    async addProduct(
        @Body('title') prodTitle: string,
        @Body('description') prodDesc: string,
        @Body('price') prodPrice: number
    ) {
        const productId = await this.productsService.insertProduct(prodTitle, prodDesc, prodPrice);
        return { id: productId };
    }

    @Get()
    async getAllProducts() {
        return await this.productsService.getProducts();
    }

    @Get(':id')
    async getProduct(@Param('id') prodId: string) {
        const product = await this.productsService.getSingleProduct(prodId);
        if (!product) {
            throw new NotFoundException('Product not found');
        }
        return product;
    }

    @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')
    async removeProduct(@Param('id') prodId: string) {
        await this.productsService.deleteProduct(prodId);
        return null;
    }
}
```
 - Explanation: This controller handles HTTP requests related to products. It defines routes for adding, retrieving, updating, and deleting products, and delegates the actual logic to the ProductsService.
 - Why it's important: Controllers define the API endpoints and handle incoming HTTP requests. They keep the endpoint logic separate from the business logic, promoting code organization and maintainability.

products/product.module.ts

```
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ProductsController } from './products.controller';
import { ProductsService } from './products.service';
import { ProductEntity } from './product.entity';

@Module({
  imports: [TypeOrmModule.forFeature([ProductEntity])],
  controllers: [ProductsController],
  providers: [ProductsService],
})
export class ProductsModule {}
```
 - Explanation: This module encapsulates the products-related components: the controller, service, and entity. It imports the TypeOrmModule to provide database-related functionalities.
 - Why it's important: Modules help organize code into cohesive units and define the boundaries of the application. They encapsulate related functionality and promote code reusability and maintainability.

products/product.service.ts

```
import { Injectable, NotFoundException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { ProductEntity } from "./product.entity";

@Injectable()
export class ProductsService {
  constructor(
    @InjectRepository(ProductEntity)
    private readonly productRepository: Repository<ProductEntity>,
  ) {}

  async insertProduct(title: string, desc: string, price: number) {
    const newProduct = await this.productRepository.create({
      title,
      description: desc,
      price,
    });
    await this.productRepository.save(newProduct);
    return newProduct.id;
  }

  async getProducts() {
    return await this.productRepository.find();
  }

  async getSingleProduct(productId: string) {
    return await this.productRepository.findOne({ where: { id: productId } });
  }
  

  async updateProduct(productId: string, title: string, desc: string, price: number) {
    const product = await this.productRepository.findOne({ where: { id: productId } });
    if (!product) {
      throw new NotFoundException('Could not find product');
    }
    product.title = title || product.title;
    product.description = desc || product.description;
    product.price = price || product.price;
    await this.productRepository.save(product);
  }
  

  async deleteProduct(prodId: string) {
    const result = await this.productRepository.delete(prodId);
    if (result.affected === 0) {
      throw new NotFoundException('Could not find product');
    }
  }
}
```
 - Explanation: This service contains methods to perform CRUD operations on products. It interacts with the database using the ProductEntity and TypeORM.
 - Why it's important: Services encapsulate business logic and data manipulation tasks. They provide a clean separation between data access and controller logic, promoting code maintainability and testability.

Understanding the files and their functionalities in the products folder is crucial for building a complete NestJS application with CRUD operations for managing products. These files work together to handle HTTP requests, interact with the database, and provide a seamless user experience.

## Conclusion

Congratulations! You've successfully created and explored a basic NestJS project. This is just the beginning of your journey with NestJS. As you continue to explore the framework, you'll discover its rich features and capabilities for building robust server-side applications.

In the next steps, you can experiment with adding new routes, services, and modules to extend the functionality of your NestJS application. Don't hesitate to refer to the official NestJS documentation for more in-depth explanations and examples.

Happy coding!

github:https://github.com/adewale-codes/nest_crud