Diving Into Design Metadata with NestJS

Diving Into Design Metadata with NestJS

Design-time metadata makes it possible to take advantage of features like reflection or dependency injection in JavaScript. Have you ever wondered how it is it possible that either Angular or NestJS automatically resolves relations between your classes? Or how can you access method param types to perform auto-validation of input parameters? Let's have an in-depth look at TypeScript metadata and see what we can achieve using this powerful mechanism.

2c6d09f60e512f388508afe9bebfcd6f?s=128

Kamil Mysliwiec

October 20, 2019
Tweet

Transcript

  1. KAMMYSLIWIEC DIVING INTO TYPESCRIPT 
 DESIGN METADATA 
 WITH NESTJS

  2. KAMMYSLIWIEC KAMIL MYSLIWIEC CREATOR OF NESTJS | CO-FOUNDER OF TRILON.IO


    GOOGLE DEVELOPER EXPERT @KAMMYSLIWIEC

  3. KAMIL MYŚLIWIEC SOFTWARE ENGINEER
 @KAMMYSLIWIEC


  4. KAMMYSLIWIEC Adonis Express Koa Nest Fastify

  5. NESTJS WWW.NESTJS.COM
 @NESTFRAMEWORK
 KAMMYSLIWIEC

  6. DESIGN-TIME METADATA

  7. KAMMYSLIWIEC PARAMETER TYPE 
 METADATA

  8. DEPENDENCY INJECTION

  9. KAMMYSLIWIEC @Injectable() class CatsService { private httpService = new HttpService();

    private logger = new Logger(); }
  10. KAMMYSLIWIEC @Injectable() class CatsService { private httpService = new HttpService();

    private logger = new Logger(); }
  11. KAMMYSLIWIEC @Injectable() class CatsService { constructor( private httpService: HttpService, private

    logger: Logger, ) {} }
  12. KAMMYSLIWIEC @Injectable() class CatsService { constructor( private httpService: HttpService, private

    logger: Logger, ) {} }
  13. KAMMYSLIWIEC @Injectable() class CatsService { constructor( private httpService: HttpService, private

    logger: Logger, ) {} }
  14. KAMMYSLIWIEC @Injectable() class CatsService { constructor( private httpService: HttpService, private

    logger: Logger, ) {} }
  15. KAMMYSLIWIEC const Injectable: ClassDecorator = (target: Object) => {};

  16. KAMMYSLIWIEC COMPILER 
 METADATA

  17. KAMMYSLIWIEC @Injectable() class CatsService { constructor( private httpService: HttpService, private

    logger: Logger, ) {} }
  18. KAMMYSLIWIEC CatsService = __decorate( [__metadata('design:paramtypes', [HttpService, Logger])], CatsService, );

  19. KAMMYSLIWIEC CatsService = __decorate( [__metadata('design:paramtypes', [HttpService, Logger])], CatsService, );

  20. KAMMYSLIWIEC CatsService = __decorate( [__metadata('design:paramtypes', [HttpService, Logger])], CatsService, );

  21. KAMMYSLIWIEC @Injectable() class CatsService { constructor( private httpService: HttpService, private

    logger: Logger, ) {} }
  22. KAMMYSLIWIEC @Injectable() class CatsService { constructor( private httpService: HttpService, private

    logger: Logger, ) {} }
  23. REFLECTION
 API

  24. KAMMYSLIWIEC Reflect.getMetadata('design:paramtypes', CatsService);

  25. KAMMYSLIWIEC Reflect.getMetadata('design:paramtypes', CatsService);

  26. KAMMYSLIWIEC Reflect.getMetadata('design:paramtypes', CatsService);

  27. KAMMYSLIWIEC const metadata = Reflect.getMetadata( 'design:paramtypes', CatsService, ); // metadata

    = [HttpService, Logger]
  28. PIPES

  29. KAMMYSLIWIEC @Injectable() class ExponentialStrengthPipe implements PipeTransform { transform( value: number,

    metadata: ArgumentMetadata, ): number {} }
  30. KAMMYSLIWIEC @Injectable() class ExponentialStrengthPipe implements PipeTransform { transform( value: number,

    metadata: ArgumentMetadata, ): number {} }
  31. KAMMYSLIWIEC @Injectable() class ExponentialStrengthPipe implements PipeTransform { transform( value: number,

    metadata: ArgumentMetadata, ): number {} }
  32. KAMMYSLIWIEC @Injectable() class ExponentialStrengthPipe implements PipeTransform { transform( value: number,

    metadata: ArgumentMetadata, ): number {} }
  33. KAMMYSLIWIEC interface ArgumentMetadata { type: 'body' | 'query' | 'param'

    | 'custom'; metatype?: Type<any>; data?: string; }
  34. KAMMYSLIWIEC @Post() async create( @Body() createDto: CreateRecipeDto, ): Promise<string> {

    return 'This action adds a new recipe'; } TYPE METATYPE DATA
  35. KAMMYSLIWIEC @Post() async create( @Body() createDto: CreateRecipeDto, ): Promise<string> {

    return 'This action adds a new recipe'; } TYPE METATYPE DATA
  36. KAMMYSLIWIEC const metadata = Reflect.getMetadata( 'design:paramtypes', new HostClass(),
 'create' );

    // metadata = [CreateRecipeDto]
  37. KAMMYSLIWIEC REFLECTION API
 CHALLENGES

  38. KAMMYSLIWIEC INTERFACES

  39. KAMMYSLIWIEC @Injectable() class CatsService { constructor( private httpService: HttpService, private

    logger: Logger, ) {} }
  40. KAMMYSLIWIEC @Injectable() class CatsService { constructor( private httpService: IHttpService, private

    logger: Logger, ) {} }
  41. KAMMYSLIWIEC const metadata = Reflect.getMetadata( 'design:paramtypes', CatsService, ); // metadata

    = [Object, Logger]
 // IHttpService === Object
  42. KAMMYSLIWIEC POTENTIAL 
 SOLUTION

  43. KAMMYSLIWIEC GENERICS

  44. KAMMYSLIWIEC @Injectable() class CatsService { constructor( private httpService: HttpService, private

    logger: Logger, ) {} }
  45. KAMMYSLIWIEC @Injectable() class CatsService { constructor( private httpService: HttpService, private

    catsRepository: Repository<Cat>, ) {} }
  46. KAMMYSLIWIEC const metadata = Reflect.getMetadata( 'design:paramtypes', CatsService, ); // metadata

    = [HttpService, Repository] // what about Cat class?
  47. KAMMYSLIWIEC POTENTIAL 
 SOLUTION

  48. KAMMYSLIWIEC CIRCULAR
 DEPENDENCIES

  49. KAMMYSLIWIEC @Injectable() class CatsService { constructor( private dogsService: DogsService, )

    {} }
  50. KAMMYSLIWIEC @Injectable() class DogsService { constructor( private catsService: CatsService, )

    {} }
  51. KAMMYSLIWIEC const metadata = Reflect.getMetadata( 'design:paramtypes', CatsService, ); // metadata

    = [undefined] // DogsService === undefined
  52. KAMMYSLIWIEC POTENTIAL 
 SOLUTION

  53. KAMMYSLIWIEC @Injectable() class CatsService { constructor( @Inject(forwardRef(() => DogsService)) private

    dogsService: DogsService, ) {} }
  54. KAMMYSLIWIEC function forwardRef(forwardRefFn: ForwardRefFn): Type<any> { (<any>forwardRefFn).__forward_ref__ = forwardRef; ...


    }
  55. KAMMYSLIWIEC function forwardRef(forwardRefFn: ForwardRefFn): Type<any> { (<any>forwardRefFn).__forward_ref__ = forwardRef; ...


    }
  56. KAMMYSLIWIEC TYPE METADATA

  57. KAMMYSLIWIEC export class User { email: string; password: string; }

  58. KAMMYSLIWIEC export class User { email: string; password: string; }

  59. KAMMYSLIWIEC const metadata = Reflect.getMetadata( 'design:type', new User(),
 'email' );

    // metadata = undefined
  60. KAMMYSLIWIEC const metadata = Reflect.getMetadata( 'design:type', X ‘design:paramtype’ new User(),


    'email' ); // metadata = undefined
  61. KAMMYSLIWIEC const metadata = Reflect.getMetadata( 'design:type', new User(),
 'email' );

    // metadata = undefined
  62. KAMMYSLIWIEC export class User { email: string; password: string; }

  63. KAMMYSLIWIEC export class User { @Property email: string; @Property password:

    string; }
  64. KAMMYSLIWIEC const metadata = Reflect.getMetadata( 'design:type', new User(),
 'email' );

    // metadata = String
  65. KAMMYSLIWIEC WHAT’S THE POINT?

  66. None
  67. KAMMYSLIWIEC export class User { @Property email: string; @Property password:

    string; }
  68. KAMMYSLIWIEC User: type: object properties: email: type: string password: type:

    string
  69. KAMMYSLIWIEC LIMITATIONS

  70. KAMMYSLIWIEC export class User { @Property email: string; @Property password:

    string; }
  71. KAMMYSLIWIEC export class User { @Property email: string; @Property password:

    string; @Property tags: string[]; }
  72. KAMMYSLIWIEC const metadata = Reflect.getMetadata( 'design:type', new User(),
 'tags' );

    // metadata = Array
  73. KAMMYSLIWIEC export class User { @Property email: string; @Property password:

    string; @Property tags: string[]; }
  74. KAMMYSLIWIEC export class User { @Property email: string; @Property password:

    string; @Property tags: Array<string>; }
  75. KAMMYSLIWIEC POTENTIAL 
 SOLUTION

  76. KAMMYSLIWIEC TYPESCRIPT
 PLUGINS

  77. KAMMYSLIWIEC const emitResult = program.emit( undefined, undefined, undefined, undefined, {

    before: [genericsMetadataPlugin], after: [], },
 );
  78. KAMMYSLIWIEC const emitResult = program.emit( undefined, undefined, undefined, undefined, {

    before: [genericsMetadataPlugin], after: [], },
 );
  79. KAMMYSLIWIEC RETURN TYPE METADATA

  80. KAMMYSLIWIEC @Post() create(): string { return 'This action adds a

    new recipe'; }
  81. KAMMYSLIWIEC const metadata = Reflect.getMetadata( ‘design:returntype', new HostClass(),
 'create' );

    // metadata = String
  82. KAMMYSLIWIEC const metadata = Reflect.getMetadata( ‘design:returntype', X ‘design:type’ new HostClass(),


    'create' ); // metadata = String
  83. KAMMYSLIWIEC const metadata = Reflect.getMetadata( 'design:returntype', new HostClass(),
 'create' );

    // metadata = String
  84. KAMMYSLIWIEC WHY IS IT USEFUL?

  85. KAMMYSLIWIEC LIMITATIONS

  86. KAMMYSLIWIEC @Post() async create(): Promise<string> { return 'This action adds

    a new recipe'; }
  87. KAMMYSLIWIEC const metadata = Reflect.getMetadata( 'design:returntype', new HostClass(),
 'create' );

    // metadata = Promise // what about “String”?
  88. RECAP

  89. KAMMYSLIWIEC OPEN SOURCE

  90. KAMMYSLIWIEC KAMMYSLIWIEC WWW.TRILON.IO

  91. KAMMYSLIWIEC OPEN SOURCE

  92. KAMMYSLIWIEC KAMMYSLIWIEC THANK
 YOU @KAMMYSLIWIEC