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