Nest uses Expres under the hood

npm i -g @nestjs/cli
nest new --strict project-name
# crud generator
nest g resource [name]

Controller

nest g controller [name]
import { Controller, Get } from '@nestjs/common';

@Controller('cats') // main prefix path
export class CatsController {
  @Post()
  @HttpCode(204) // default 200
  @Header('Cache-Control', 'none') // or inject @Res() res: Response
  @Redirect('https://nestjs.com', 301) // or inject @Res() res.redirect
  create() {
    return 'This action adds a new cat';
  }

  @Get() // auxilary path (allows regex symbols (* acts like .))
  findAll(@Req() req: Request): string { // any method name
    return 'This action returns all cats';
  }

  @Get('observable')
  findAllTemp(): Observable<string[]> { // we can also use Promises
    // returns an observable, which is a stream of data that can be consumed by an entity;
    // nest returs the last emited value
    return new Observable((observer) => {
      observer.next(['1', '2', '3']);
      observer.complete();
    });
  }

  @Get(':id') // should be declared after any static routes
  findOne(@Param() params: any): string {
    // or (@Param('id') id: string)
    console.log(params.id);
    return `This action returns a #${params.id} cat`;
  }
}

Decorators

@Get(), @Post(), @Put(), @Delete(), @Patch(), @Options(), @Head() and @All()

@Request(), @Req()	        req
@Response(), @Res()*	    res --> adding this decorator puts a nest to a library specific mode, so the programmer is responsible to call .send() or .json()
> Note: you lose compatibility with Nest features that depend on Nest standard response handling, such as Interceptors and @HttpCode() / @Header() decorators. To fix this, you can set the passthrough option to true
> so you can set cookies etc. but leave all the rest to the framework

@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

DTO (Data transfer object)

We can use TS interfaces, but for more features like “pipes” we should use ES6 classes

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

....

 @Post()
  create(@Body() createCatDto: CreateCatDto) {
    return 'This action adds a new cat';
  }

Provider

nest g service cats
Can be injected
Controllers should handle HTTP requests and delegate more complex tasks to providers.
Providers are plain JavaScript classes that are declared as providers in a module.

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }
}

...

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}

Scopes

By default, Nest makes provider to be a singleton, which means that there is only one instance of the provider throughout the application.
@Injectable({ scope: Scope.REQUEST })
@Injectable({ scope: Scope.TRANSIENT })
@Injectable({ scope: Scope.DEFAULT })

Request

Request-scoped providers are not shared across requests. All dependent providers on a request-scoped provider will be request-scoped as well.

Transient

Transient providers are not shared at all. Each consumer that injects a transient provider will receive a new, dedicated instance of that provider. However, all dependent providers of a transient provider will be shared.

@Injectable({ scope: Scope.TRANSIENT })
export class HelloService {
constructor(@Inject(INQUIRER) private parentClass: object) {} // we'll get parent class instance 
}
Durable providers

Similar to the request scope, durability bubbles up the injection chain. That means if A depends on B which is flagged as durable, A implicitly becomes durable too (unless durable is explicitly set to false for A provider).

Custom providers

Providers can include any value not just services

const connectionFactory = {
  provide: 'CONNECTION', // token
  useFactory: () => {
    const connection = createConnection(options);
    return connection;
  },
};

Optional providers

@Injectable()
export class HttpService<T> {
  constructor(@Optional() private httpClient: HttpClient<T>) {}
}

Mocking providers

import { CatsService } from './cats.service';

const mockCatsService = {
  /* mock implementation
  ...
  */
};

Custom names of providers

@Injectable()
export class CatsRepository { // previously provider was  declared as 'CONNECTION'
  constructor(@Inject('CONNECTION') connection: Connection) {}
}

@Module({
  imports: [CatsModule],
  providers: [
    {
      provide: CatsService, 
      useValue: mockCatsService,
    },
  ],
})
export class AppModule {}

Dynamic providers (useClass)

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

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

Factory providers (useFactory)

const connectionProvider = {
  provide: 'CONNECTION',
  useFactory: (optionsProvider: OptionsProvider, optionalProvider?: string) => {
    const options = optionsProvider.get();
    return new DatabaseConnection(options);
  },
  inject: [OptionsProvider, { token: 'SomeOptionalProvider', optional: true }],
  //       \_____________/            \__________________/
  //        This provider              The provider with this
  //        is mandatory.              token can resolve to `undefined`.
};

@Module({
  providers: [
    connectionProvider,
    OptionsProvider,
    // { provide: 'SomeOptionalProvider', useValue: 'anything' },
  ],
})
export class AppModule {}

Use existing provider (useExisting)

we can use existing provider useExisting instead of useClass or useValue

Async providers

const connectionFactory = {
  provide: 'CONNECTION',
  useFactory: async () => {
    const connection = await createConnection(options);
    return connection;
  },
}; // won't start a module until the promise is resolved

Module

nest g module cats
Module should encapsulate a closely related set of capabilities which are self-contained.

  • providers - services (encapsulated by the module, can’t be used outside, if not exported)
  • controllers - controllers
  • imports - other modules
  • exports - providers that should be available in other modules
src
   cats
     dto
       create-cat.dto.ts
     interfaces
       cat.interface.ts
     cats.controller.ts
     cats.module.ts
     cats.service.ts
  app.module.ts
  main.ts

Global module

By default we can’t use provders from other modules without importing them.

@Global() // makes module global
@Module({
  imports: [CatsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Dynamic modules

Dynamic module is a module that is created dynamically, at runtime.

@Module({
  imports: [ConfigModule],
})
export class DatabaseModule {
  static register(options: Options): DynamicModule {
    return {
      module: DatabaseModule, // the onlye required property
      providers: [
        {
          provide: DATABASE_CONNECTION,
          useValue: createDatabaseConnection(options),
        },
      ],
    };
  }
}

...
@Module({
  imports: [
    DatabaseModule.register({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
    }),
  ],
})
Async dynamic modules
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<ConfigModuleOptions>()
  .setExtras(
    {
      isGlobal: true,
    },
    (definition, extras) => ({
      ...definition,
      global: extras.isGlobal,
    }),
  )
  .build();

...

@Module({
  imports: [
    ConfigurableModuleClass.registerAsync({
      useFactory: async (configService: ConfigService) => ({
        configService,
      }),
      inject: [ConfigService],
    }),
  ],
})

Middleware

Calls before controller

Class middleware

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}

Set in module

@Module({
  imports: [CatsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats'); // path /cats
    // or .forRoutes({ path: 'cats', method: RequestMethod.GET });
    // or .forRoutes(CatsController);
      .exclude(
        { path: 'cats', method: RequestMethod.GET },
        { path: 'cats', method: RequestMethod.POST },
        'cats/(.*)',
      )
  }
}

Functional middleware

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
}

Global middleware

const app = await NestFactory.create(AppModule);
app.use(logger);

Exception filters

Nest layer will catch all exceptions and return a standard response.
Nest provides a build-in exceptions (like BadRequestException, etc.)

@Get()
cats() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

Full control

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}
Method scoped level
@Get()
@UseFilters(new HttpExceptionFilter())
// or
// @UseFilters(HttpExceptionFilter) --> prefered way, Nest would reuse the same instance of the filter
cats() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
Controller scoped level
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
Global scoped level

It is not possible to inject dependencies into filters when they are declared globally.
There are workarounds though

const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());

Pipes (validation/transforming input data)

9 pipes are provided by Nest (throws errors on null or undefined, use new DefaultValuePipe() to set default value) Transforms input data to the desired form or validates the data.

Binding pipe (parameter scoped)

@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
  // or if we want to customize
  // @Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))
  return this.catsService.findOne(id);
}

Binding pipe (method scoped)

@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

‘class-validator’ package

Allows to use decorators to validate DTOs

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';

@Injectable()
export class ValidationPipe implements PipeTransform<any> {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToClass(metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

Guard (authorization/authentication)

Used for authentication and authorization
@Injectable() + CanActivate
Guards are executed after all middleware, but before any interceptor or pipe.
Middleware is dumb, it doesn’t know anything about the route, it just executes before the route handler.

@Controller('cats')
@UseGuards(RolesGuard) // if returns false, Nest will throw a ForbiddenException, which would be handled by the exception layer, or any other exception filter
@Roles('admin') // custom decorator
export class CatsController {
  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}

Reflector

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    const hasRole = () => user.roles.some((role) => roles.includes(role));

    return user && user.roles && hasRole();
  }
}

Interceptors (aop - aspect oriented programming)

Aop allows to change the behavior of classes, methods, or functions by adding additional code (interceptors) before or after the existing code. @Injectable + implements NestInterceptor

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');

    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`After... ${Date.now() - now}ms`)),
      );
  }
}

...

@Get()
@UseInterceptors(LoggingInterceptor)
findAll() {
  return 'This action returns all cats';
}

Custom Decorators

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
// later we can combine decorators
@Roles('admin')
@UseGuards(RolesGuard)
@UseGuards(AuthGuard)

// or

export const Auth = (...roles: string[]) => applyDecorators(
  SetMetadata('roles', roles),
  UseGuards(RolesGuard),
  UseGuards(AuthGuard),
);

...

@Auth('admin')