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
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
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;
}
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;
}
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,
});
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);
}
}
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 });
}
}
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) {}
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 {}
// 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 {}
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)