Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Nest.js Brings TypeScript to Node.js and Express, Part 1

DZone's Guide to

Nest.js Brings TypeScript to Node.js and Express, Part 1

In this article, we take an introductory look at the Nest.js framework. We'll cover what it does, how to use it, and more!

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

In this article, we are going to learn about Nest.js, a framework for building Node.js web applications. Why Nest.js? Because, although Node.js already contains a lot of libraries to develop web applications, none of them effectively address one of the most important subjects: the architecture. As we will see, Nest.js introduces various building blocks that help developers to better organize Node.js applications. This GitHub repository contains the final code developed throughout this article!

Nest.js Introduction

Nest.js is a new framework in the already cluttered Node.js landscape. What makes it different from other frameworks is that Nest.js leverages TypeScript to help developers effortlessly build highly testable, scalable, loosely coupled, and easily maintainable applications. For example, when building an application, developers will define TypeScript classes decorated with @Controller() to handle HTTP request. Developers will also create classes that implement the NestMiddleware interface to define Express middlewares.

For those who already know and use Angular, the syntax and the components that Nest.js introduces to backend development will be quite familiar.

Let's take a look at the most important building blocks used to compose Nest.js applications.

Modules

Modules on Nest.js are the most basic building blocks. Through modules, we encapsulate related code that composes our application, like components and controllers. To start Nest.js, we need to inform the root module of our application. Although not needed, breaking an app into multiple modules is advised, as it helps to keep different matters encapsulated.

For example, let's say that we have an application that manages users and their personal finances. Even though personal finances are related to users, we would rather avoid high coupling between the classes and low cohesion by splitting them into separate modules.

Defining a module on Nest.js is simple:

import { Module } from '@nestjs/common';
import { UsersService } from './users.service';

@Module({
    components: [UsersService],
    exports: [UsersService]
})
export class UsersModule {}

In this case, we are exporting a module called UsersModule that contains a single component, UsersService. As this component is defined in the exports property of the @Module decorator, other modules that import UsersModule will be able to use it.

To start a new Nest.js application using UsersModule as the root module is as simple as calling NestFactory like this:

import { NestFactory } from '@nestjs/core';
import { UsersModule } from './modules/users.module';

async function bootstrap() {
    const app = await NestFactory.create(UsersModule);
    await app.listen(3000);
}
bootstrap();

Running this code will trigger a Nest.js application and bootstrap all components defined on UsersModule.

Controllers

As in many other platforms (like Spring and ASP.NET.aspx), controllers on Nest.js are responsible for handling HTTP requests. To define a new controller on a module we need to:

  • Create a new class.
  • Decorate the class with @Controller()
  • Add the controller to the module definition.

As we want this controller to expose some content or accept new data, we also need to create a method marked with one endpoint decorator (e.g. @Get or @Post). Therefore, this is the minimum code we need to create a controller:

import {Controller, Get} from '@nestjs/common';

@Controller('friendly-guy')
export default class FriendlyGuyController {

    @Get()
    sayHi() {
        return "Howdy!"
    }
}

And this is how we add the controller to a module:

import { Module } from '@nestjs/common';
import FriendlyGuyController from './friendly-guy-controller';

@Module({
    modules: [],
    controllers: [FriendlyGuyController]
})
export class ApplicationModule {}

Components

Nest.js allows developers to create components that can be injected into other components or controllers. A component usually plays one of two roles on Nest.js applications. The first one is the Service role, when a component contains some business logic. The second one is the Repository role, when a component abstracts away the interaction with databases.

For example, let's say that we have a Service that handles checkouts on a store. To allow a checkout to consolidate, the Service has to interact with a Repository to check if there are enough items in the inventory for the desired product. In this scenario, we would have the Service defined as follows:

import { Component } from '@nestjs/common';

@Component()
export class CheckoutService {
  constructor(private readonly inventoryRepository: InventoryRepository) {}

  checkout(cart: ShoppingCart) {
    for (let item of cart.items) {
      let inventory = this.inventoryRepository.getInventory(item.product);
      if (inventory.size < item.quantity) {
        throw new Error("Not enough items");
      }
    }
    // ...
  }
}

The CheckoutService, in this case, is a @Component() that can be injected on a controller or on another component. Besides that, the component itself depends on a repository called InventoryRepository. This repository is then used to retrieve the inventory for a specific product, so the service can check if there are enough items to be sold.

As Nest.js heavily uses the Dependency Injection design pattern, it's easy to manage dependencies between the building blocks. We just have to define dependencies in the constructor of a controller or component, and Nest.js will provide an instance for us.

// ...
export class CheckoutService {
  constructor(private readonly inventoryRepository: InventoryRepository) {}
  // ...
}

Middlewares

Whenever we want to act on a request before it reaches a controller, we can create a Middleware. On Nest.js, middlewares are classes that implement the NestMiddleware interface and that are decorated with @Middleware(). This interface expects us to define a concrete implementation of the resolve method to return an Express middleware: (req, res, next) => void.

Below we can see an example of a middleware that enables CORS:

import { Middleware, NestMiddleware, ExpressMiddleware } from '@nestjs/common';

@Middleware()
export class CorsMiddleware implements NestMiddleware {
    resolve(): ExpressMiddleware {
        return (req, res, next) => {
            res.header('Access-Control-Allow-Origin', '*');
            next();
        };
    }
}

To activate this middleware, we need to make our module implement NestModule to provide a concrete method definition of the configure method:

import { Module, NestModule, MiddlewaresConsumer, RequestMethod } from '@nestjs/common';
import { CorsMiddleware } from './cors.middleware';

@Module({})
export class ApplicationModule implements NestModule {
    configure(consumer: MiddlewaresConsumer): void {
        consumer.apply(CorsMiddleware).forRoutes(
            { path: '/example', method: RequestMethod.GET }
        );
    }
}

In this case, CorsMiddleware has been activated only for GET requests that aim the /example path on our application. The official documentation provides further explanation on how to use middlewares.

Exception Filters

One great feature provided by Nest.js is the addition of a layer responsible for catching unhandled exceptions. On this layer, we can define custom Exception Filters. To define an exception filter, we have to:

  1. Create a class that implements the ExceptionFilter
  2. Decorate it with @Catch()
  3. Implement the catch(exception: HttpException, response) method.

For example, let's suppose that we have a custom exception called BusinessException. If we want to provide a default message to users whenever this exception occurs, we can create an exception filter like this:

import { ExceptionFilter, Catch } from '@nestjs/common';
import { BusinessException } from './business.exception.ts';

@Catch(BusinessException)
export class BusinessExceptionFilter implements ExceptionFilter {
  catch(exception: BusinessException, response) {
    response.status(status).json({
      message: 'Oh, no! You are not doing business right!',
    });
  }
}

Then we can make a controller use this exception handler by using the @UseFilters() decorator:

import { UseFilters } from `@nestjs/common`;

@UseFilters(new BusinessExceptionFilter())
export class BusinessController {}

Or we can make it a global exception handler by making our app instance use it:

async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);

  // Adding global exception handler
  app.useGlobalFilters(new HttpExceptionFilter());

  await app.listen(3000);
}
bootstrap();

Building a Nest.js Application

Now that we understand the building blocks available on Nest.js, let's create a small application with this framework. Through this app, we will be able to see some of the core concepts of Nest.js in action.

Nest.js provides two easy ways to start a new application. We could clone the Nest.js TypeScript Starter project available on GitHub, or we could use the CLI tool for Nest.js applications. Both alternatives are equally good and provide a solid foundation to build apps. Though, as we want to understand how a Nest.js application is built, we are going to create a new one from scratch.

What We'll Build

During the rest of the article, we are going to create a small RESTful API that enables users to create and retrieve companies. To keep things simple, we will handle companies without interacting with any external database. That is, we will hold companies in memory and instances of companies will be lost on an eventual reboot.

The application, although small, will help us understand what pieces a Nest.js application is made of and how these pieces work together. In the end, we will have the same configuration we would get by using the Nest.js CLI tool or by cloning the starter project.

Initializing npm

A Nest.js application is nothing more than a Node.js app. As such, we will create a new directory to hold the source code of our app and will use npm to manage our dependencies. We achieve that by running the following commands:

# create a new directory
mkdir nest-companies

# move to new directory
cd nest-companies

# starts NPM with default properties
npm init -y

The last command, npm init -y, starts the nest-companies directory as a Node.js project by creating the package.json file with default properties.

Adding Dependencies

With this file in place, we can add the minimum dependencies to boot a Nest.js application. To do that, we use npm as follows:

npm i @nestjs/common \
      @nestjs/core \
      @nestjs/microservices \
      @nestjs/websockets \
      rxjs \
      typescript \
      reflect-metadata

npm i -D @types/node \
         ts-node

There is no way we can create a Nest.js app with less dependencies than this. Therefore it's worthwhile to understand why we have to add each dependency above.

  • The first dependency, @nestjs/common, adds the most commonly used components on a Nest.js application. For example, having this dependency we can define a Module for our application, as we will see soon.
  • The second dependency, @nestjs/core, defines the core functionality of Nest.js. Through this library, we can create, among other things, an instance of NestApplication to run our Nest.js module.
  • The third dependency, @nestjs/microservices, won't be used by us directly. Even though this library is internally used by @nestjs/common, we need to add it as a dependency because Nest.js team marked it as a peer dependency. An issue on GitHub has been created a while ago to discuss why @nestjs/core is required as a peerDependency on @nestjs/common. Though, as nothing concrete has been achieved yet, we need to add the dependency explicitly.
  • Just like the @nestjs/microservices, @nestjs/websockets is also required internally by Nest.js. Therefore, the solution, for now, is the same, explicitly add it as a dependency of our project.
  • The fifth dependency is a well-known library: rxjs. As this library's README file on GitHub states, rxjs is a set of libraries to compose asynchronous and event-based programs using observable collections and Array#extras style composition in JavaScript.
  • TypeScript, the sixth dependency, is a superset of JavaScript that compiles to clean JavaScript output. This language adds types, classes, and modules to JavaScript. It helps developers to be more productive and produce more reliable code through tooling that supports autocompletion and type checking. The whole Nest.js framework has been written in TypeScript, and using this language is advised when developing applications with this framework.
  • The last runtime dependency, reflect-metadata, is also used internally by the Nest.js framework while it's marked as a peer dependency. This library gives Nest.js the ability to manage decorators on runtime through a reflective API. Being a peer dependency, we also explicitly define it in our project.

Besides these seven runtime dependencies, we also needed to define two development dependencies on our app. The first one, @types/node, provides TypeScript definition for the Node.js API. With it, we can use count on TypeScript to check if our code is valid while interacting with Node.js directly. The second development dependency, ts-node, allows us to execute TypeScript files directly without prior transpilation to JavaScript. Using this library on production is not advised, as it adds a lot of burden to the process.

Tune in next time when we'll go over several other aspects of Nest.js!

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Topics:
web dev ,node.js ,nest.js ,javascript framework

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}