Smells like Angular Spirit:

NestJS as natural backend for Angular

@ValentinKononov

Valentin Kononov

Full-Stack Developer

Speaker

@akveo_inc

@ValentinKononov

Slides

  • https://slides.com/valentinkononov/smells-like-angular-spirit
 

@ValentinKononov

  • https://docs.nestjs.com
 

Agenda

@ValentinKononov

  • Intro
  • Tune in NestJS
  • NestJS Framework Members
    • Controllers
    • Dependency Injection
  • Benefits
  • Authentication Sample
  • Conclusion

Intro. NestJS Philosophy

How it's connected to Angular?

  • Typescript

  • Framework

    • Out-Of-The-Box Features

  • Inspired by Angular

    • Same Look and Feel as Angular

    • Decorators usage to define Metadata

@ValentinKononov

Tune in NestJS

Dive into Code Samples

Installation

npm install -g @nestjs/cli
npm run start
install node v8.9.0 or higher
nest new Angular-Belarus-Meetup-App

@ValentinKononov

Main.ts

@ValentinKononov

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: new WinstonLogger(),
    cors: true,
  });

  // app configuration here

  await app.listen(3001);
}

bootstrap();

AppModule.ts

@ValentinKononov

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

@Module({
  imports: [
    AuthModule,
    UserModule,
    MongooseModule.forRoot(
        'mongodb://localhost:27017/bundle-node-nest'),
    WinstonModule.forRoot({    }),
  ],
  exports: [...],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

NestJS Framework Parts

out of the box

NestJS Framework Parts

@ValentinKononov

Modules

Controllers

Routing

Dependency Injection

Middleware

Pipes

Guards

Exceptions

Interceptors

Controller

@ValentinKononov

import { Controller, Get, Post, Put, Delete, Body, Param, Req } 
    from '@nestjs/common';

@Controller('api/users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get(':id')
  async getById(@Req() req, @Param() id: string) {
    return await this.userService.findById(id);
  }

  @Post()
  async create(@Body() user: User) {
    return await this.userService.create(user);
  }
}

Dependency Injection

@ValentinKononov

import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class UserService {

 public async create(user: any): Promise<UserDto> {
    return await createdUser.save();
  }

  public async findById(id: string): Promise<UserDto> {
    return await repository.findById(id);
  }
}

Module.exports

Module.providers

Put To

Guards

@ValentinKononov

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}
@Controller('api/users')
@UseGuards(AuthGuard)
export class UserController {
    ...
}
// main.ts

const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new AuthGuard());

Interceptors

@ValentinKononov

@Injectable()
export class TrackInterceptor implements NestInterceptor {
  intercept(
    context: ExecutionContext, next: CallHandler
  ): Observable<any> {
    logger.log(`Request Started`);
    return next.handle()
        .pipe(
            tap(() => logger.log(`Request completed`)),
        );
  }
}
@Controller('api/users')
@UseInterceptors(TimeTrackingInterceptor)
export class UserController {
    ...
}
// main.ts

const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new TimeTrackingInterceptor());

Authentication

steps and code sample

Define Strategy

@ValentinKononov

import { ExtractJwt, Strategy } from 'passport-jwt';
...
import { PassportStrategy } from '@nestjs/passport';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.AUTH_JWT_SECRET,
    });
  }

  async validate(payload: JwtPayload) {
    const user = await this.authService.validateUser(payload);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

Import Modules

@ValentinKononov

import { JwtModule } from '@nestjs/jwt';
...
import { PassportModule } from '@nestjs/passport';

@Module({
  imports: [
    PassportModule.register({ defaultStrategy: 'jwt' }),
    JwtModule.register({
      secretOrPrivateKey: config.auth.jwt.secret,
      signOptions: {
        expiresIn: config.auth.jwt.ttl,
      },
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService, JwtStrategy],
  exports: [PassportModule, AuthService],
})
export class AuthModule {}

Use Guards

@ValentinKononov

import { Controller } from '@nestjs/common';
import { UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Controller('api/users')
@UseGuards(AuthGuard('jwt'))
export class UserController {
  ...
}

Create Token

@ValentinKononov

import { JwtService } from '@nestjs/jwt';

private getTokenPayload(user: User): JwtPayload {
    return {
      email: user.email,
      role: user.role,
      id: user.id,
    };
  }

public async signIn(user): Promise<Token> {
    const expiresIn = config.auth.jwt.ttl;
    const token = this.jwtService.sign(
          this.getTokenPayload(user),
          { expiresIn });
    return { token };
  }

Benefits

  • Code Organization

  • Out of the Box feature

  • Same Look and Feel as Angular

  • Real code sharing

  • Compatibility with TS and JS

  • Good Documentation

@ValentinKononov

Conclusion

or instead of cons

  • SOLID node framework based on Express

    • but you need to follow the structure

  • If you don't like Angular it might be hard

  • Could be excess for pure node developer

Thanks!

See you again!

@ValentinKononov

Made with Slides.com