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

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

DZone's Guide to

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

Welcome back! We pick up where we left off in Part 1 and show you how to make and secure a web application using Nest.js. Let's get to it!

· Web Dev Zone ·
Free Resource

Bugsnag monitors application stability, so you can make data-driven decisions on whether you should be building new features, or fixing bugs. Learn more.

Bootstrapping Nest.js Applications

After installing all dependencies, we now have to create the following files to bootstrap a Nest.js application:

  1. A file that contains the definition of the root module of our application.
  2. A file to create a Nest.js instance with our root module.
  3. A file to load ts-node to run our source code without transpiling it.
  4. A file to configure TypeScript to our needs.

Note that the third file will be used during development only. On other environments, like staging or production, we would run a pre-transpiled version of our app.

Before creating these files, let's create a directory to hold the source code:

# creating src folder
mkdir -p src

We will define our root module by creating a file called app.module.ts in the src directory and by adding the following code to it:

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

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

To start the Nest.js application with this module we will create a file called server.ts in the src directory with the following code:

import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';

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

Then we will create the file that will load ts-node and execute our application. We will call this file index.js and add it to the root directory of our project with the following code:

require('ts-node/register');
require('./src/server');

Lastly, we will indicate that our project is a TypeScript project by creating a file called tsconfig.json in the root directory with the following code:

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": false,
    "noImplicitAny": false,
    "removeComments": true,
    "noLib": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es6",
    "sourceMap": true,
    "allowJs": true,
    "outDir": "./dist"
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

Note that the most important properties in this configuration for a Nest.js application is the presence of emitDecoratorMetadata and experimentalDecorators. We need to activate decorators on any Nest.js application since the framework heavily uses this TypeScript feature to define controllers, modules, components, etc.

Having these four files in place, we can now start our application by running the following command:

node index

Creating Nest.js Controllers

Even though our application is up and running, we can't do much with it now as there are no controllers to accept requests. Therefore, let's create our first Nest.js controller. We will define this controller in a new file called companies.controller.ts that we are going to create in the src directory with the following code:

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

@Controller('companies')
export class CompaniesController {

    @Get()
    getCompanies() {
        return ["Coke", "Apple", "Tesla"]
    }
}

This controller is as simple as it gets. For now, we defined a single endpoint that responds with a list of companies when users send HTTP requests to the /companies path. To make this controller available, we need to add it to the controllers property of the @Module decorator of our root module (./src/app.module.ts):

import {Module} from '@nestjs/common';
import {CompaniesController} from './companies.controller';

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

If we run our application again, we will be able to get the list of companies as shown here:

# starting the application
node index

# getting the list of companies
curl localhost:3000/companies

# ["Coke","Apple","Tesla"]

Serializing Classes on Nest.js

On real applications, we would usually be interested in handling more properties besides the company name. To do this, we will create a class that represents a company, add more properties to it, and refactor our controller to use with this class.

Let's create this class in a new file called company.ts in the ./src/ directory with the following code:

export class Company {
    name: string;
    industry: string;
    address: string;

    constructor(name: string, industry: string, address: string) {
        this.name = name;
        this.industry = industry;
        this.address = address;
    }
}

In this case, we've created a simple representation of a company with two properties: the name of the company and the industry where the company acts. After creating this class, let's refactor the CompaniesController class as follows:

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

@Controller('companies')
export class CompaniesController {

    private companies = [
      new Company("Coke", "Soda"),
      new Company("Apple", "Computers"),
      new Company("Tesla", "Cars")
    ];

    @Get()
    getCompanies() {
        return this.companies;
    }
}

The changes to the controller, although small, give us a more realistic scenario that we would face while developing production-ready Nest.js applications. We started by importing the Company class into our controller. After that, we defined a static array containing three instances ofCompany. Then we ended up changing the getCompanies() method implementation to return this array instead of an array of strings.

Running the application and issuing a GET request to /companies will now return the list of companies with their name and industry:

# starting the application
node index

# getting the list of companies
curl localhost:3000/companies

# [{"name":"Coke","industry":"Soda"} ,{"name":"Apple","industry":"Computers"} ,{"name":"Tesla","industry":"Cars"}]

Note that, on Nest.js, we don't need to instruct the controller to send the response as JSON. We simply return what we want and the framework serializes the returned object(s) as JSON.

Deserializing Classes on Nest.js

Even though Nest.js serializes responses as JSON by default, the other way around is not true. To accept JSON requests and transform them into instances of our classes, we need to install and configure body-parser manually. Luckily, the process is simple. First, we install the package by issuing npm install body-parser, then we update the ./src/server.ts file to make our app use body-parser:

import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';
import * as bodyParser from 'body-parser';

async function bootstrap() {
    const app = await NestFactory.create(ApplicationModule);
    app.use(bodyParser.json());
    await app.listen(3000);
}
bootstrap();

With these two extra lines of code (the import statement and app.use(...)), we can now accept requests with JSON objects. Let's test this feature by adding a new method in the CompaniesController class to accept POST requests:

import {Controller, Get, Post, Body} from '@nestjs/common';
import {Company} from './company';

@Controller('companies')
export class CompaniesController {
    // companies array definition ...
    // getCompanies method definition ...

    @Post()
    createCompany(@Body() company: Company) {
        this.companies.push(company);
    }
}

The createCompany method that we just created contains two decorators. The first one, @Post(), indicates that this method is responsible for handling POST requests targeted to /companies. The second decorator, @Body(), indicates that the framework needs to deserialize the request body into an instance of Company. With these changes, we can now add new companies to the array of companies that are defined in the CompaniesController class:

# starting the application
node index

# getting the list of companies
curl localhost:3000/companies

# [{"name":"Coke","industry":"Soda"} ,{"name":"Apple","industry":"Computers"} ,{"name":"Tesla","industry":"Cars"}]

curl -X POST -d '{
  "name" :"Nestle",
  "industry": "Foods"
}' -H "Content-Type: application/json" localhost:3000/companies

# getting the new list of companies
curl localhost:3000/companies

# [{"name":"Coke","industry":"Soda"}, {"name":"Apple","industry":"Computers"}

Securing Nest.js Applications

The Nest.js framework creates, in the end, just an Express application. Therefore, we can easily use JWTs to secure Nest.js applications with Auth0 and get state-of-the-art user management features. To do that, we'll need an Auth0 account to manage authentication. To sign up for a free Auth0 account, let's follow this link. Next, let's set up an Auth0 API to represent our app.

To create the API, let's go to APIs in our Auth0 dashboard and click on the "Create API" button. There we can enter nest-companies as the name for the API and set the Identifier to our API endpoint URL. In this case, this is http://localhost:3000/. The Signing Algorithm must be RS256.

Having created the API, we can now implement Auth0 authentication on our Nest.js application. The first step is to install three dependencies:

npm install express-jwt \
            jwks-rsa \
            @types/express-jwt

The first dependency, express-jwt, facilitates the creation of a middleware that validates JWTs. The second dependency, jwks-rsa, is a library to retrieve RSA public keys from a JWKS (JSON Web Key Set) endpoint. The third one, @types/express-jwt, provides the TypeScript definition of the express-jwt library. We don't need to install a TypeScript definition of jwks-rsa because this library already ships with one.

Following the Nest.js way of doing things, we are going to create a @Middleware() to set upexpress-jwt and jwks-rsa in our app. Let's create this middleware in a new file called authentication.middleware.ts in the ./src directory with the following code:

import { Middleware, NestMiddleware, ExpressMiddleware } from '@nestjs/common';
import * as jwt from 'express-jwt';
import {expressJwtSecret} from 'jwks-rsa';

@Middleware()
export class AuthenticationMiddleware implements NestMiddleware {
    resolve(): ExpressMiddleware {
        return jwt({
            secret: expressJwtSecret({
                cache: true,
                rateLimit: true,
                jwksRequestsPerMinute: 5,
                jwksUri: `https://${CLIENT_DOMAIN}/.well-known/jwks.json`
            }),
            audience: 'http://localhost:3000/',
            issuer: 'https://${CLIENT_DOMAIN}/',
            algorithm: 'RS256'
        });
    }
}

Note that we need to replace both ${CLIENT_DOMAIN} placeholders in the code above by our own Auth0 client domain. In my case, I will replace these placeholders with bkrebs.auth0.com so the app can check that https://bkrebs.auth0.com/ is the issuer and check keys on the https://bkrebs.auth0.com/.well-known/jwks.json JWKS.

Then, to activate this middleware and make our endpoints secure, we need to refactor ApplicationModule as follows:

import {Module, NestModule, MiddlewaresConsumer, RequestMethod } from '@nestjs/common';
import {CompaniesController} from './companies.controller';
import {AuthenticationMiddleware} from './authentication.middleware';

@Module({
    modules: [],
    controllers: [CompaniesController]
})
export class ApplicationModule implements NestModule {
    configure(consumer: MiddlewaresConsumer): void {
        consumer.apply(AuthenticationMiddleware).forRoutes(
            { path: '/**', method: RequestMethod.ALL }
        );
    }
}

In this case we are securing all endpoints in our application (/**) from all request methods (GETPOST, etc). To see the middleware in action, we need to restart our application, fetch a JWT from Auth0, then send it in the Authorization header of our requests:

# start the application
node index

# fetch JWT from Auth0
curl -X POST -H 'content-type: application/json' -d '{
    "client_id": "$CLIENT_ID",
    "client_secret":"$CLIENT_SECRET",
    "audience":"http://localhost:3000/",
    "grant_type":"client_credentials"
}' https://bkrebs.auth0.com/oauth/token

# copy the access_token and issue in the Authorization header
curl -H 'authorization: Bearer $JWT' http://localhost:3000/companies

# trying to access the enpoint without a JWT won't work
curl http://localhost:3000/companies

Note that we need to replace $CLIENT_ID and $CLIENT_SECRET in the second command above. We can get these values from the nest-companies (Test Client) client that Auth0 generated for us. Besides that, we also need to replace $JWT in the third command with the access_token returned by Auth0.

Final Thoughts

Nest.js is a new framework that relies on a mature approach for building web applications on Node.js. From the very beginning, the authors of this framework followed best practices and managed to create a well-structured framework. As we saw in this article, creating applications with Nest.js is easy, flexible, and intuitive. Added to this, TypeScript enhances the code quality of applications built with Nest.js by introducing type safety in our backend applications and by facilitating the development process (e.g. eventual refactorings).

Monitor application stability with Bugsnag to decide if your engineering team should be building new features on your roadmap or fixing bugs to stabilize your application.Try it free.

Topics:
web dev ,nest.js ,web application development ,node.js

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}