Nestjs is a server-side framework often confused with the term "Server-Side Angular"
Even though Nest follows the pattern & design principals of Google's Angular but its significantly different than Angular by design
Nestjs is a abstraction layer over traditional nodejs server-side tools & packages
So please don't compare it with http-servers such as: express, koa, fastify, hapi etc.. Nestjs actually uses express & fastify as its platform for http server
Nestjs centralizes all the needed technologies/tools to build perfect, reliable & durable enterprise servers using Nodejs. Its in the league of Django, Spring Boot, Ruby on Rails etc.. server-side frameworks
It follows micro-services architecture but can be used for monolithic servers too
Features of Nestjs: (source: docs.nestjs.com)
- Extensible, Reliable, Versatile, Progressive framework
- Offers clean, straight forward & understandable architecture
- Offers out of the box:
- Dependency Injection,
- Routing with decorators using Controllers
- Security with
helmet
,cors
,node-csrf
- Exception filters (unhandled exception layer)
- Guards
- Separation of logic from controllers using Providers
- Robust Module system
- Lifecycle events & many more
- unit-testing & integration-testing support with
jest
&super-test
- It provides/supports (through packages):
- http-server (express/fastify)
- GraphQL server
- websocket server (socket.io / ws)
- database orm (sequelize/mongoose/typeorm/knex/prism)
- request body validation using
class-validator
- caching using
cache-manager
- task-scheduling using
cron
- task queue using
bull
& many more other tools
This is not a complete tutorial. If you want to learn Nestjs completely visit docs.nestjs.com. It contains complete details about Nestjs
All of the names & technical terms might feel over whelming but these are pretty easy to implement. Some of 'em take 5-10 lines of code only to implement. But each of them are equally important for a enterprise server or a server with smaller user-base. Nestjs covers the architecture & the dependencies for us
A fact, Nestjs actually helps & guides us as a new backend developer towards all important tools along being used as a tool
Nestjs also has a powerful cli, named @nestjs/cli
. It helps manipulating files/modules. Its kinda like Angluar's CLI but only handles files & modules. It helps you organizing your project more efficiently
I'll be doing a 3 parts of Nestjs tutorial. In part-1 or this part, I'll cover about only
Controllers
,ExceptionFilters
&Providers
But first lets create the project using:
$ npm install -g @nestjs/cli
$ nest new hello-world && cd hello-world
$ npm run start:dev
This will create a directory structure of following:
hello-world/
βββ src/
β βββ app.controller.ts
β βββ app.controller.spec.ts
β βββ app.module.ts
β βββ app.service.ts
β βββ app.service.spec.ts
β βββ main.ts
βββ test/
β βββ app.e2e-spec.ts
β βββ jest.e2e.json
βββ .gitignore
βββ .eslintrc.js
βββ .prettierrc
βββ README.md
βββ nest-cli.json
βββ package.json
βββ package-lock.json
βββ tsconfig.build.json
βββ tsconfig.json
Now lets create a directory hello
inside src
& inside hello
create 4 files for this tutorial
hello.controller.ts
hello.module.ts
hello.service.ts
hello-body.dto.ts
1. Controllers
Controllers are Nest's building block. These are where one will handle incoming request. You can define the route path with http method modifiers (Get, Post, Put, Delete etc..) decorators
Controller example:
// hello.controller.ts
import {Controller, Logger, Get, NotFoundException, Param} from "@nestjs/common"
@Controller()
export class HelloController{
/* a logger from nestjs for logging error/other info */
logger: Logger = new Logger(HelloController.name)
db: {id: string, message: string}[] = []; // temporary database
@Get("hello")
async replyHello() {
try {
return "Hello";
} catch (error) {
this.logger.error(error?.message ?? "");
throw error;
}
}
@Get("hello/:helloId") // dyanmic parameter just like express, koa-router etc...
async replyExactHello(
/*pass the same dynamic parameter from "hello/:helloId" in
@Param decorator's first to let nestjs find the parameter
correctly
*/
@Param("helloId") id: string
) {
try {
/*returning the correct temp hello message*/
const message = this.db.find(hello=>hello.id===id)?.message
if(!message) throw new NotFoundException("desired `hello` not found") //404 error
return message;
} catch (error) {
/* will log the error & autmatically send the error as response with all required data */
this.logger.error(error?.message ?? "");
throw error;
}
}
}
Nestjs uses decorator pattern & its primarily written in Typescript but it supports JavaScript too. You can also perform validation on request-body using class-validator
////// main.ts //////
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./hello.module";
import { ValidationPipe } from "@nestjs/common";
async function bootstrap() {
/* Creates a nest application for monolithic servers */
const app = await NestFactory.create(AppModule, { logger: false });
// validation is done through Nestjs pipes.
// Nestjs Pipes run before a specified request (kinda like middlewre)
/* Enabling Validation pipe globally.
Thoough it can be done in module level too
*/
app.useGlobalPipes(new ValidationPipe());
await app.listen(PORT);
}
bootstrap()
Here I'm just creating a Nestjs server instance & adding ValidatonPipe
globally. Pipes are just methods that run before Controllers. Pipes can be used at method/argument level also using @UsePipes
decorator. You can even create your own custom Pipes. Also you might've noticed AppModule
. It's the main point of Nestjs for making all the things work. You'l find more about AppModule
after Exception Filters section
Now lets create a body validation Schema with class-validator
& decorators
////// hello-body.dto.ts //////
import {IsDefined, IsNotEmpty} from "class-validator"
export class HelloBodyDTO{
@IsDefined()
@IsNotEmpty({message: "A custom error message if you want to"})
message!: string;
}
@IsDefined
& @IsNotEmpty
will validate a string which is defined & at least has a length of 1 or in other words the string shouldn't be just ""
. Now lets use this one in a @Post
request controller:
////// hello.controller.ts //////
import {Controller, Logger, Get, NotFoundException, Post, Body} from "@nestjs/common"
import {HelloBodyDTO} from "./hello-body.dto"
import {v4 as uuid} from "uuid"
@Controller()
export class HelloController{
// ... previously written stuff from the `Controller` part
// decorator name is similar to http verbs e.g. POST -> @Post
@Post("hello")
saveHello(
/*Just pass the class as a type & the validation will be done automatically*/
@Body() body: HelloBodyDTO
){
try{
const id = uuid()
const hello = {id, message: body.message}
this.db.push(hello) // saving in the temp db
return hello;
}
catch (error){
this.logger.error(error?.message ?? "");
throw error;
}
}
}
2. Exception Filters
Exception filters are error handlers that runs when a Controller throws error. It handles that error automatically & sends appropriate, user-friendly error response
If you've followed the code you might've got a hint that Nestjs uses a HttpExceptionFilter
by default (globally) . The package @nestjs/common
provides many HttpException
inherited Exceptions e.g. NotFoundException
, BadRequestException
, NotAcceptableException
, UnauthorizedException
and many more. You can even create your very own custom ExceptionFilter
Learn how to create a custom ExceptionFilter
If you want to use a custom exception filter in a route handler, you've to use @UseFilter
decorator
// ... other stuff
import {ForbiddenException} from "@nestjs/common"
import {CustomHttpExceptionFilter} from "./custom-filter"
// a simple handler method inside a Controller
@Post("throw-error")
@UseFilters(new CustomHttpExceptionFilter())
errorThrowingHandler(){
throw new ForbiddenException("its forbidden");
}
// ... other stuff
Using this long code @UseFilters(new HttpExceptionFilter())
before every handler/controller can't be hard but if your application has a usecase for using it globally then you just have to use useGlobalFilters
of Nestjs server instance & pass all the global filters as parameters
///// main.ts /////
// ...other stuff
import {CustomHttpExceptionFilter} from "./custom-filter"
app.useGlobalFilters(new CustomHttpExceptionFilter())
// ...other stuff
3. Providers
Providers are another essential part of Nestjs. By far, I was using a temporary variable to store data. That's why such simple thing can be done through Controller handlers. But for bigger, complex logic, it'd be hard to do code separation & reuse. That's where providers comes to play....
You can declare/create a provider using the @Injectable
decorator on top of a class. Then Dependency injection/Logging etc can be done through providers
Here's a provider example. I'm using a custom variable as a database for ease of understanding. But most of the time create
, find
, findById
, delete
, deleteById
etc are provided by the database ORM. So in real-world scenarios these methods aren't needed to be implemented in Providers. Providers should be used for handling more complex logic. But for demonstration lets think these methods as complex logic
////// hello.service.ts ///////
import { Injectable } from "@nestjs/common"
import {v4 as uuid} from "uuid"
@Injectable()
export class HelloService{
db: {id: string, message: string}[] = []
async findById(id: string){
return this.db.find(hello=>hello.id===id)
}
async create(message: string){
const id = uuid()
const hello = {id, message}
this.db.push(hello)
return hello;
}
async deleteById(id: string){
this.db = this.db.filter(hello=>hello.id!==id)
return `DELETED node ${id} from db`
}
}
Now, lets convert the HelloController
for using HelloService
through Dependency Injection. But before we've to put HelloService
inside the HelloModule
A module is a class annotated with a
@Module()
decorator. The@Module()
decorator provides metadata that Nest makes use of to organize the application structure. Each application has at least one module, a root module. The root module is the starting point Nest uses to build the application graph - the internal data structure Nest uses to resolve module and provider relationships and dependencies
That module is the main thing that helps Nest making the dependency graph for Dependency Injection. Example of an app.module.ts
:
////// app.module.ts //////
import { Module } from '@nestjs/common';
/*This is the base '/' controller */
import { AppController } from './app.controller';
/* basic provider for AppController */
import { AppService } from './app.service';
@Module({
/*this where descendent modules get added
we've to do this if we were importing another inside
an other module to be able to use its providers
*/
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
We've to add every providers (Injectable
)/controllers that we use inside our controller/provider in a module. Lets put HelloService
& HelloController
in HelloModule
:
////// hello.module.ts //////
import {Module} from "@nestjs/common"
import {HelloService} from "./hello.service"
import {HelloController} from "./hello.controller"
@Module({
/* put all providers that is under this module graph to help Nest to
inject those in the controllers
*/
providers: [HelloService],
/* put controllers here for letting Nest recognize all the route/path &
their handlers
*/
controllers: [HelloController],
/*put those providers which you wanna use outside of this module
In an outside module when HelloModule gets imported
*/
exports: []
})
export class HelloModule{}
To let Nest recognize HelloModule
as module, lets add HelloModule
inside the imports
array of AppModule
:
///// app.module.ts //////
// ... previously imported stuff
import {HelloModule} from "./hello/hello.module"
@Module({
imports: [HelloModule],
// ... other properties previously added
})
export class AppModule {}
And this
AppModule
is used as the entrypoint ofNestFactory
instance inmain.ts
Now we can easily use HelloService
inside HelloController
or in other Module's controllers/providers
////// hello.controller.ts //////
// ... previously imported stuff
import {HelloService} from "./hello.service"
@Controller()
export class HelloController{
logger: Logger = new Logger(HelloController.name)
/* just create a contructor arg and set the type as the provider
& that's gonna do the work
*/
constructor(private readonly helloService: HelloService){}
@Get("hello")
async replyHello() {
try {
return "Hello";
} catch (error) {
this.logger.error(error?.message ?? "");
throw error;
}
}
@Get("hello/:helloId")
async replyExactHello(@Param("helloId") id: string) {
try {
/*using provider's methods*/
const message = await this.helloService.find(id)?.message;
if(!message) throw new NotFoundException("desired `hello` not found") //404 error
return message;
} catch (error) {
this.logger.error(error?.message ?? "");
throw error;
}
}
@Post("hello")
saveHello(@Body() body: HelloBodyDTO){
try{
/* creating `hello` using the provider HelloService */
return await this.helloService.create(body.message)
}
catch (error){
this.logger.error(error?.message ?? "");
throw error;
}
}
}
Don't be afraid of Nestjs's Module system. Its hard for the first time but once you get the idea, it all makes sense & this module system is required for Nestjs to do all those cool Dependecy Injection.
BTW, you don't have to add providers/controllers manually in the Module. If you create modules/providers/controller using the nest-cli, it'll be done automatically. Above mentioned Module managing steps can be done automatically just by using these 3 commands
create a module:
$ nest g module hello
create a controller:
$ nest g controller hello
create a provider:
$ nest g provider hello
Please don't hurt meπΆ. I know, I should've shown this easier way earlierπ. But that idea of how module works in Nestjs often troubles people to not use Nestjs. So its important to getting the deep insight of it. You can take deep insight of Nestjs Module system here
Here's the complete application