Nest.js Fundamentals

How

Why

What

Nest.js

What is Nest.js?

Nest is a framework for building efficient, scalable Node.js server-side applications

What

Fully supports Typescript

Wraps around robust HTTP server frameworks like Express (the default).

Provides a layer of opinionated structure above these common Node.js frameworks (Express/Fastify), though their native APIs are exposed to the developer if needed.

Why Nest?

Why

Why use Nest.js?

Typescript compatible

Monorepo support

Use it to create monoliths or microservices

Powerful CLI

Dependency Injection / IOC container

Framework adaptive

Domain drived development

Built-in exception filter

Use third-party modules

Why

Installation

Install the Nest CLI globally and use it to
generate a nest starter project (recommended)

To get started you can:

Use the Nest CLI via npx 

Clone the nest.js starter project using Git

npm i -g @nestjs/cli
nest new my-project-name

# or...
npx nest new my-project-name

# or...
git clone https://github.com/nestjs/typescript-starter.git

How

Fundamentals

Controllers

Providers

Modules

How

Controllers

Controllers are responsible for handling incoming requests and returning responses to the client.

The routing mechanism decides
which controller receives which request

/vets
/pets
/sitters

What

Typically, each controller handles a collection of routes

and has several methods to perform different actions.

Controllers

pets.controller.ts
import { Controller, Get } from '@nestjs/common';

@Controller('pets')
export class PetsController {
  
  @Get()
  getAllPets(): string {
    return 'This action returns all pets';
  }
  
}

How

Nest provides all standard HTTP request methods

as decorators that trigger the class methods respectively

Route By Verbs

@Get()         @Delete()

@Post()        @Options()

@Put()         @Head()

@Patch()       @All()

The class methods names don't really matter other than readability...

How

https://pet-portal.io/pets/123

Route Parameters

pets.controller.ts
import { Controller, Get } from '@nestjs/common';

@Controller('pets')
export class PetsController {
  
  @Get(':id')
  getPetByID(@Param('id') id: string) {
    return `Get pet by ID: ${id}`;
  }
  
}

How

Pattern based routes are supported as well.

Route wildcards


  @Get('ab*cd')
  findAll() {
    return 'This route uses a wildcard';
  }

The 'ab*cd' route path will match abcd, ab_cd, abecd, and so on.
The characters ?, +, *, and () may be used in a route path, and are subsets of their regular expression counterparts. The hyphen ( - ) and the dot ( . ) are interpreted literally by string-based paths.
* This feature is borrowed from express.js

How

Nest provides access to the request object of the underlying platform (Express by default)

Request object

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

@Controller('pets')
export class PetsController {
  @Get()
  getAllPets(@Req() request: Request): string {
    return 'This action returns all pets';
  }
}
pets.controller.ts

How

@Request(), @Req() req
@Response(), @Res()* res
@Next() next
@Session() req.session
@Param(key?: string) req.params / req.params[key]
@Body(key?: string) req.body / req.body[key]
@Query(key?: string) req.query / req.query[key]
@Headers(name?: string) req.headers / req.headers[name]
@Ip() req.ip
@HostParam() req.hosts

Available Decorators

How

The response status code is always 200 by default,
except for POST requests which are
201.
 

We can easily change this behavior by adding
the
@HttpCode(...) decorator at a handler level.

Status code

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new pet';
}

How

How

Use a @Header( ) decorator

Use a library-specific response object  ( and call res.header( ) directly )

Headers

@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new pet';
}

To specify a custom response header, you can:

Redirection

@Get('/outdated')
@Redirect('/updated-url', 301)
oldHandler() {
  return 'This action is never called...';
}

Use a @Redirect( ) decorator

Use a library-specific response object  ( and call res.redirect( ) directly )

statusCode defaults to 302 unless specifically stated otherwise

To redirect a response URL, you can:

How

Dynamic Redirection

{
  "url": string,
  "statusCode": number
}

Returned values will override any arguments passed to the @Redirect() decorator.

For example:

To determine the HTTP status code or the redirect URL dynamically.
Return an object from the route handler method with the following shape:

@Get('docs')
@Redirect('https://docs.pets-portal.io', 302)
getDocs(@Query('version') version) {
  if (version === '4') {
    return { url: 'https://docs.pets-portal.io/v4/' };
  }
}

How

Asynchronicity

Nest supports and works well with async functions.

@Get()
async findAll(): Promise<any[]> {
  return [];
}

Async functions return a Promise.
The deferred value
will be resolved by Nest.

@Get()
findAll(): Observable<any[]> {
  return of([]);
}

Nest route handlers are also able to return RxJS observable streams.
Nest will subscribe to the source underneath and take the last emitted value (once the stream is completed).

Both of the above approaches work and you can use whatever fits your requirements.

How

Request Payloads

A DTO is an object that defines how the data will be sent over the network.

export class UpdatePetDto {
  readonly name?: string;
  readonly city?: string;
  readonly specie?: string;
  readonly breed?: string;
}

 Usage:

@Put(':id')
updatePet(@Param('id') id: string, @Body() updatePetDto: UpdatePetDto) {
  return `This action updates a pet document record`
}
pets.controller.ts

Although DTOs can be declared as interfaces it is recommended to use classes since those are preserved as real entities after compilation to ES6 while interfaces are removed.

How

Demo time!

How

Providers

The main idea of a provider is that "wiring up" instances can be delegated to the Nest runtime system

Providers can be any class annotated with the @Injectable() decorator.  Those can be services, repositories, factories, helpers, and so on.
 

What

Services

A common provider use-case is a service that is typically responsible for data retrieval and used by a  Controller  class.

import { Injectable } from '@nestjs/common';
import { Pet } from './entities/pet.entity';

@Injectable()
export class PetsService {
  private pets: Pet[] = [];

  getAllPets() :Pet[] {
    return this.pets;
  }
  createPet(pet: Pet) {
    this.pets.push(pet);
  }
}
pets.service.ts

Since we want to use an instance of this class in another, we decorate it with @Injectable()
 

We then register it as a provider.

This enables us to define an instance property at the Controller class constructor, which will be injected by Nest.

How

Provider Registration

Provider registration is done at a Module level.
under the
providers array of the @Module( ) decorator.

The Controller class registered under the controllers array can now consume the service.

import { Module } from '@nestjs/common';
import { PetsController } from './pets.controller';
import { PetsService } from './pets.service';

@Module({
  controllers: [PetsController],
  providers: [PetsService],
})
export class PetsModule {}
pets.module.ts

How

Dependency injection

Now that the Provider and the controller are registered at a Module level. We can consume the service at the constructor of the Controller class and let Nest inject it for us.

import { PetsService } from './pets.service';

@Controller('pets')
export class PetsController {
  
  constructor(private readonly petsService: PetsService) {}

  @Get()
  getAllPets() {
    return this.petsService.getAllPets();
  }

}
pets.controller.ts

How

Custom providers

How

In some cases, you may want more control over.

for example, you might need to:

Create a custom instance imperatively.

Use a different class depending on env vars...

Override a class with a mocked object value for testing

We can use custom providers:

Value providers  - useValue

Class providers  - useClass

Factory providers  - useFactory

Alias providers  - useExisting

Standard providers - registration

How

@Module({
  controllers: [PetsController],
  providers: [PetsService],
})
@Module({
  controllers: [PetsController],
  providers: [
    {
      provide: PetsService,
      useClass: PetsService,
    }
  ],
})

Explicit long form syntax

This is just a shorthand syntax

for the most common use case which is useClass

as shown below... 

useValue - example

How

@Module({
  imports: [PetsModule],
  providers: [
    { provide: 'CONNECTION',  useValue: 'http://...', },
  ],
})
export class AppModule {}
@Injectable()
export class PetsRepository {
  constructor(@Inject('CONNECTION') connection: string) {}
}

Registration

Injection

useClass - example

How

const configServiceProvider = {
  provide: ConfigService,
  useClass:
    process.env.NODE_ENV === 'development'
      ? DevelopmentConfigService
      : ProductionConfigService,
};

@Module({
  providers: [configServiceProvider],
})
export class AppModule {}

Use a different class depending on env vars...

useFactory - example

How

const connectionFactory = {
  provide: 'CONNECTION',
  useFactory: (optionsProvider: OptionsProvider) => {
    const options = optionsProvider.get();
    return new DatabaseConnection(options);
  },
  inject: [OptionsProvider],
};

@Module({
  providers: [connectionFactory],
})
export class AppModule {}

The factory function can accept (optional) arguments.

The (optional) inject property accepts an array of providers Nest will pass as arguments to the factory function during instantiation

useExisting - example

How

@Injectable()
class LoggerService {
  /* implementation details */
}

const loggerAliasProvider = {
  provide: 'AliasedLoggerService',
  useExisting: LoggerService,
};

@Module({
  providers: [LoggerService, loggerAliasProvider],
})
export class AppModule {}

The useExisting syntax allows you to create aliases for existing providers.

Assume we have two different dependencies, one for 'AliasedLoggerService' 
and one for
LoggerService.

They'll both resolve to the same instance.

Provider scope

How

A provider can have any of the following scopes:

DEFAULT

REQUEST

TRANSIENT

A Singleton instance whose lifetime is tied directly to the application lifecycle. Once the application has bootstrapped,
all singleton providers have been instantiated. (The default)

A new instance of the provider is created exclusively for each incoming request. Then garbage-collected upon completion.

Each consumer that injects a transient provider receives
a new, dedicated instance.

Demo time!

How

Modules

Each application has at least one module - the root module.
It is used as a starting point to build the application graph that resolves the dependencies of other modules and providers.

What

Feature Modules

Modules are an effective way to organize components by app features.
Organizing code relevant to a specific feature establishes clear boundaries and helps us manage complexity as the application and/or team size grow...

It is a good practice to have a folder per module, containing all of the module dependencies (controller, providers, dto's, entities, tests etc...)

What

Shared Modules

Every module is automatically a shared module.
Once created it can be reused by any module.

Modules are singletons by default and can be imported
by multiple other modules.

What

Module definition

How

providers           Instantiated by Nest and used at least in this module

controllers       Controllers with API routes handled in this module

imports             Other modules that export providers required in this module

exports              Providers in this module that should be available

                             in other modules

A module is a class annotated with a @Module() decorator.

The @Module() decorator takes an object with the following properties:

Export Providers

How

Providers are scoped to its declaring module.

To make them visible to other modules, they must be exported.

import { Module } from '@nestjs/common';
import { PetsController } from './pets.controller';
import { PetsService } from './pets.service';

@Module({
  controllers: [PetsController],
  providers:   [PetsService],
  exports:     [PetsService],
})
export class PetsModule {}
pets.module.ts

Module re-exporting

How

Modules can export their internal providers.

They can also re-export modules that they import.

@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class SomeModule {}

Global Modules

How

When you want a set of providers to be available everywhere
out-of-the-box ( helpers, database connections, etc...),

make the module global with the @Global( ) decorator.

import { Module, Global } from '@nestjs/common';
import { PetsController } from './pets.controller';
import { PetsService } from './pets.service';

@Global()
@Module({
  controllers: [PetsController],
  providers: [PetsService],
  exports: [PetsService],
})
export class PetsModule {}
pets.module.ts

Dynamic Modules -  create

How

Dynamic modules enable us to create customizable modules
that can register and configure providers dynamically at runtime.

import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from './config.service';

@Module({})
export class ConfigModule {
  static register(options): DynamicModule {
    const providers = createConfigProviders(options);
    return {
      module: ConfigModule,
      providers: providers,
      exports: providers,
    };
  }
}
config.module.ts

Dynamic Modules  -  consume

How

Consuming dynamic modules enables us to pass options at runtime.

* think of a generic plugin architecture that accepts a settings object

app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './config/config.module';

@Module({
  imports: [ConfigModule.register({ folder: './config' })],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Demo time!

How

Nest.js fundamentals

By Yariv Gilad

Nest.js fundamentals

  • 1,067