DEV Community

Cover image for A brief introduction to NestJS
Royce
Royce

Posted on • Updated on

A brief introduction to NestJS

What is NestJS and why you should choose it for your next project?

       NestJS is a Node.js framework intended to be used with TypeScript to build scalable and efficient server-side applications. It is open-source, progressive, easily extensible, and quickly gaining popularity among developers. Under the hood, Nest makes use of Express, another HTTP server framework, but can also be configured with Fastify. Nest can easily be integrated with any SQL or NoSQL database and provides integration with TypeORM (Object–relational mapping tool for Typescript) right out of the box for convenience. NestJS is noticeably influenced by Angular and the two would be a perfect pair for anyone’s next full-stack application.

Kickstarting your next project

       Getting started with NestJS simple. You can either scaffold the project using the Nest CLI or clone a starter project. I am going to initiate a project using the CLI (documentation is linked below for additional details).

$ npm i -g @nestjs/cli 
$ nest new project-name
Enter fullscreen mode Exit fullscreen mode

       After running these commands, the Nest CLI will scaffold your new project, create a new project directory, and populate the directory with the initial core files and supporting modules. Alternatively, you can install the core dependencies found in the docs (linked below) and build your project from the ground up.

  src
   ├── app.controller.spec.ts
   ├── app.controller.ts
   ├── app.module.ts
   ├── app.service.ts
   ├── main.ts
Enter fullscreen mode Exit fullscreen mode

Building Blocks

       If you've used Angular before this should look familiar and you'll probably feel right at home using Nest. To get our feet wet using NestJS, we're going to build a basic REST API using Nest. We'll also use a basic MongoDB database and Postman to test our endpoints.

Controllers
       The Controller is the routing mechanism responsible for handling incoming requests and returning responses to the client. Well start by defining our DTO (data transfer object) since we're using Typescript. The DTO defines how the data will be sent over the network.

// create-item.dto.ts
export class CreateItemDto {
  readonly name: string;
  readonly qty: number;
  readonly description: string;
}
Enter fullscreen mode Exit fullscreen mode

We'll also throw together our interface and Mongo Schema while we're at it..

// item.interface.ts
export interface Item {
  id?: string;
  name: string;
  qty: number;
  description?: string;
}
Enter fullscreen mode Exit fullscreen mode

The id and description in the interface is optional because mongo will provide an id us and not every item may have a description.

// item.schema.ts
import * as mongoose from 'mongoose';
export const ItemSchema = new mongoose.Schema({
  name: String,
  qty: Number,
  description: String,
});
Enter fullscreen mode Exit fullscreen mode

Now, we'll build out our controller then discuss what everything means.. (To use the CLI to generate a controller template, execute $ nest g controller items )

// items.controller.ts
import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
import { CreateItemDto } from './dto/create-item.dto';
import { ItemsService } from './items.service';
import { Item } from './interfaces/item.interface';

@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}

  @Get()
  findAll(): Promise<Item[]> {
    return this.itemsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id): Promise<Item> {
    return this.itemsService.findOne(id);
  }

  @Post()
  create(@Body() createItemDto: CreateItemDto): Promise<Item> {
    return this.itemsService.create(createItemDto);
  }

  @Delete(':id')
  delete(@Param('id') id): Promise<Item> {
    return this.itemsService.delete(id);
  }

  @Put(':id')
  update(@Body() updateItemDto: CreateItemDto, @Param('id') id): Promise<Item> {
    return this.itemsService.update(id, updateItemDto);
  }
}
Enter fullscreen mode Exit fullscreen mode

At the top we have our imports, all of which should look familiar except ItemsService which we'll build and discuss next. Then we have our @Controller() decorator, which defines our controller, establishes our endpoint /items and conveniently allows us to group our related routes. The @Get() HTTP decorator tells Nest to create a handler for a specific endpoint for HTTP requests. The @Body and @Param decorators are equivalent to req.body and req.param in Express. Nest handles that for us under the hood. findAll(), findOne(id), create(createItemDto), delete(id), and update(id, updateItemDto) are service methods we'll define in our Provider.

Providers
       In Nest, Providers can be injected as dependencies into other components and create various relationships with each other, basically "wiring up" instances of objects. Controllers handle the HTTP requests and we can delegate the more complex tasks to the Provides. There are different types of providers— services, repositories, factories, helpers, and so on. We're going to build a basic service to allow us to interact with our database. After, we'll incorporate everything in our Module.
(To use the CLI, execute $ nest g service items )

// items.service.ts
import { Injectable } from '@nestjs/common';
import { Item } from './interfaces/item.interface';
import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';

@Injectable()
export class ItemsService {
  constructor(@InjectModel('Item') private readonly itemModel: Model<Item>) {}

  async findAll(): Promise<Item[]> {
    return await this.itemModel.find();
  }

  async findOne(id: string): Promise<Item> {
    return await this.itemModel.findOne({ _id: id });
  }

  async create(item: Item): Promise<Item> {
    const newItem = new this.itemModel(item);
    return await newItem.save();
  }

  async delete(id: string): Promise<Item> {
    return await this.itemModel.findByIdAndRemove(id);
  }

  async update(id: string, item: Item): Promise<Item> {
    return await this.itemModel.findByIdAndUpdate(id, item, { new: true });
  }
}
Enter fullscreen mode Exit fullscreen mode

Following the imports, we notice the @Injectable decorator. The @Injectable decorator attahces metadata which declares that ItemsService is a class that can be managed by the Nest inversion of control (IoC) container. The rest of the code is pretty straightforward, using Mongoose methods to query our database. Going back to our controller quickly we inject it inside our constructor (if we haven't already, which generally we wouldn't have until we created it).

// items.controller.ts
@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}
Enter fullscreen mode Exit fullscreen mode

We take note of the private syntax which allows us to both declare and initialize ItemsServer immediately in the same location.

Modules
A Module is denoted with the @Module decorator and provides metadata that Nest uses to organize the application structure. Each application at least one module, a root module, usually app.module.ts, and serves as a start point Nest uses to build the application graph - the internal data structure Nest uses to resolve module and provider relationships and dependencies. In our case we will have one feature module, ItemsModule, and our root module AppModule.
(To use the CLI, execute $ nest g module items )

// items.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { ItemsController } from './items.controller';
import { ItemsService } from './items.service';
import { ItemSchema } from './schemas/item.schema';

@Module({
  imports: [MongooseModule.forFeature([{ name: 'Item', schema: ItemSchema }])],
  controllers: [ItemsController],
  providers: [ItemsService],
})
export class ItemsModule {}
Enter fullscreen mode Exit fullscreen mode
// app.module.ts
import { Module } from '@nestjs/common';
import { ItemsModule } from './items/items.module';
import { MongooseModule } from '@nestjs/mongoose';
import config from './config/keys';
const { MONGO_URI } = config;

@Module({
  imports: [ItemsModule, MongooseModule.forRoot(MONGO_URI)],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Assuming you have your database set up and have a URI in your config directory, you should be able to fire up the app with $ npm start and use Postman (or your preferred API testing software) to test out your first NestJS server side application.
I hope you give NestJS a try on you next project. I know I will. 🚀

Links:
NestJS Documentation
Dependency Injection and Inversion of Control in JavaScript

Top comments (0)