NestJS a progressive web framework

06a095e125c2016e983e183e10209b9a?s=47 jiko21
February 12, 2020

NestJS a progressive web framework

関西Node学園#9での発表資料です
GitHub: https://github.com/jiko21/nest-todo-rest

06a095e125c2016e983e183e10209b9a?s=128

jiko21

February 12, 2020
Tweet

Transcript

  1. NestJS A progressive web framework #kng9 @jiko_21 (a.k.a Daiki Kojima)

  2. Grad Student @ Kyoto univ Frontend / Serverside Engineer Android

    DeveloperͩͬͨΓ΋͢Δ Love: Vue.js, TypeScript, Kotlin, Golang… @jiko_21 (a.k.a Daiki Kojima) @jiko_21 @jiko21
  3. Agenda • NestJSͬͯԿ? • ͔ΜͨΜͳ֓ཁ • ҰൠతͳApplicationΛॻ͘ͷʹඞཁͳ஌ࣝ • NestJSͷಛ௃ •

    ·ͱΊ
  4. 2018೥ Warsaw @ PorlandͰ
 ͜Μͳηογϣϯ͕͋ͬͨ

  5. JavaScript Conference’18 @Warsaw https://youtu.be/f0qzBkAQ3mk

  6. NestJSͱ͸ • Progressive Node.js framework • TypeScriptͰهड़Մೳ • ༷ʑͳπʔϧ͕ఏڙ͞Ε͍ͯΔ

  7. ࣮ࡍʹத਎ΛݟͯΈΔ

  8. NestJSͷجૅ • module • ίʔυΛ·ͱΊͯػೳΛ࿦ཧతͳ୯Ґʹ෼ׂ
 ˠ ࠶ར༻Մೳʹ! • શͯͷNestͷΞϓϦέʔγϣϯʹ͸root module͕ଘࡏ͠


    ΞϓϦέʔγϣϯΛىಈͤ͞Δ
 (Angularͱಉ༷)
  9. NestJSͷجૅ 3PPUNPEVMF 4VC NPEVMF
 جຊతʹ͍ΖΜͳ΋ͷΛ͜Εʹ
 ٧ΊࠐΊ͍ͯ͘

  10. moduleΛߏ੒͢Δ΋ͷ

  11. moduleΛߏ੒͢Δ΋ͷ $POUSPMMFST

  12. moduleΛߏ੒͢Δ΋ͷ 1SPWJEFST $POUSPMMFST

  13. moduleΛߏ੒͢Δ΋ͷ 1SPWJEFST $POUSPMMFST *NQPSUT

  14. moduleΛߏ੒͢Δ΋ͷ 1SPWJEFST $POUSPMMFST *NQPSUT &YQPSUT

  15. moduleΛߏ੒͢Δ΋ͷ @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers: [TodosService], controllers: [TodosController], exports: [],

    }) export class TodosModule {}
  16. moduleΛߏ੒͢Δ΋ͷ @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers: [TodosService], controllers: [TodosController], exports: [],

    }) export class TodosModule {} !.PEVMF಺ʹ ͦΕͧΕΛهೖ͍ͯ͘͠
  17. Controllers

  18. Controllers • ϧʔςΟϯάΛ୲͏ • pathͱmethod͕
 ରԠ @Controller('todos') export class TodosController

    { constructor(private readonly todosService: TodosService) {} @Get() getTodos(): Promise<ITodo[]> { return this.todosService.findAll(); } @Get(':id') getTodo(@Param('id', new ParseIntPipe()) id: number): Promise<ITodo> { return this.todosService.findById(id); } @Post('') createTodo(@Body() todoDao: TodoDao): Promise<ITodo> { return this.todosService.create(todoDao) } … }
  19. Controllers • ϧʔςΟϯάΛ୲͏ • pathͱmethod͕
 ରԠ @Controller('todos') export class TodosController

    { constructor(private readonly todosService: TodosService) {} @Get() getTodos(): Promise<ITodo[]> { return this.todosService.findAll(); } @Get(':id') getTodo(@Param('id', new ParseIntPipe()) id: number): Promise<ITodo> { return this.todosService.findById(id); } @Post('') createTodo(@Body() todoDao: TodoDao): Promise<ITodo> { return this.todosService.create(todoDao) } … } ! Ξϊςʔγϣϯ Ͱ
 NFUIPEͱQBSBNɺQBUIΛઃఆ
  20. • ϧʔςΟϯάΛ୲͏ • pathͱmethod͕
 ରԠ Controllers @Controller('todos') export class TodosController

    { constructor(private readonly todosService: TodosService) {} @Get() getTodos(): Promise<ITodo[]> { return this.todosService.findAll(); } @Get(':id') getTodo(@Param('id', new ParseIntPipe()) id: number): Promise<ITodo> { return this.todosService.findById(id); } @Post('') createTodo(@Body() todoDao: TodoDao): Promise<ITodo> { return this.todosService.create(todoDao) } … }
  21. • ϧʔςΟϯάΛ୲͏ • pathͱmethod͕
 ରԠ Controllers @Controller('todos') export class TodosController

    { constructor(private readonly todosService: TodosService) {} @Get() getTodos(): Promise<ITodo[]> { return this.todosService.findAll(); } @Get(':id') getTodo(@Param('id', new ParseIntPipe()) id: number): Promise<ITodo> { return this.todosService.findById(id); } @Post('') createTodo(@Body() todoDao: TodoDao): Promise<ITodo> { return this.todosService.create(todoDao) } … } DPOTUSVDUPSʹґଘ͢Δ0CKFDUΛ ॻ͍͓ͯ͘ͱ.PEVMF͕ґଘΛղܾ %*ɺޙड़ 

  22. Providers • providers಺ͷίʔυ͸
 NestJSʹΑͬͯΠϯελϯεԽ
 ͞ΕͯModule಺Ͱڞ༗ (DI) @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers:

    [TodosService], controllers: [TodosController], exports: [], }) export class TodosModule {}
  23. Providers @Injectable() export class TodosService { constructor(@InjectRepository(Todo) todoRepo: Repository<Todo>) {}

    } @Controller('todos') export class TodosController { constructor(private readonly todosService: TodosService) {} } @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers: [TodosService], controllers: [TodosController], exports: [], }) export class TodosModule {}
  24. Providers @Injectable() export class TodosService { constructor(@InjectRepository(Todo) todoRepo: Repository<Todo>) {}

    } @Controller('todos') export class TodosController { constructor(private readonly todosService: TodosService) {} } @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers: [TodosService], controllers: [TodosController], exports: [], }) export class TodosModule {} *OKFDUBCMFΛ
 ࢦఆ
  25. Providers @Injectable() export class TodosService { constructor(@InjectRepository(Todo) todoRepo: Repository<Todo>) {}

    } @Controller('todos') export class TodosController { constructor(private readonly todosService: TodosService) {} } @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers: [TodosService], controllers: [TodosController], exports: [], }) export class TodosModule {} *OKFDUBCMFΛ
 ࢦఆ DPOUSPMMFSʹ
 ྲྀ͠ࠐΈ *OKFDU
  26. Imports • ͜ͷModule಺Ͱproviderͱ͔͕ඞཁͱ͍ͯ͠ΔModuleΛ
 ͜͜Ͱॻ͍ͯ͋͛Δ • TypeORMΈ͍ͨͳthird party module • ࣗ෼Ͱॻ͍ͨΧελϜmodule

  27. Imports @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers: [TodosService], controllers: [TodosController], exports: [],

    }) export class TodosModule {} @Module({ imports: [ TodosModule ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
  28. Imports @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers: [TodosService], controllers: [TodosController], exports: [],

    }) export class TodosModule {} @Module({ imports: [ TodosModule ], controllers: [AppController], providers: [AppService], }) export class AppModule {} JNQPSU͍ͯ͘͜͠ͱͰ
 .PEVMF͕࢖͑Δ
  29. ෼ׂ͞ΕͨModuleΛ࢖͓͏!

  30. ͱ͸͍͑…

  31. Service૚ͱ͔ͬͯɺ
 ผͷModuleͰݺͼͨ͘ͳΔΑͶ…

  32. exports • providerͷαϒηοτ • ͜͜ʹॻ͔Ε͍ͯΔ΋ͷ͸ɺ͜ͷmoduleΛimport͍ͯ͠Δ
 ଞͷmodule͔Β௚઀ར༻͕Ͱ͖Δ!

  33. JestͰςετ͕ॻ͚Δ

  34. Jestͱ͸ • Facebook੡ͷJavaScript༻Testing Framework • ଞͷTesting Libraryͱҧ͍ɺAll in one •

    ϑϩϯτΤϯυͩͱ… • React, Vue.js౳ͰΑ͘࢖ΘΕΔ…
  35. ॻ͖ํͷҰྫ describe('TodosService', () => { let service: TodosService; let repository:

    Repository<Todo>; beforeEach(async () => { ... }); afterEach(async () => { ... }); it('findAll success', async () => { expect(await service.findAll()).toEqual(TODOS_ARAY); }); });
  36. ॻ͖ํͷҰྫ describe('TodosService', () => { let service: TodosService; let repository:

    Repository<Todo>; beforeEach(async () => { ... }); afterEach(async () => { ... }); it('findAll success', async () => { expect(await service.findAll()).toEqual(TODOS_ARAY); }); }); ֤ςετέʔε௚લʹ࣮ߦ
  37. ॻ͖ํͷҰྫ describe('TodosService', () => { let service: TodosService; let repository:

    Repository<Todo>; beforeEach(async () => { ... }); afterEach(async () => { ... }); it('findAll success', async () => { expect(await service.findAll()).toEqual(TODOS_ARAY); }); }); ֤ςετέʔε௚લʹ࣮ߦ ֤ςετέʔε௚ޙʹ࣮ߦ
  38. JestͰ͸Α͋͘Γ͕ͪͳ͜ͱ… • ୯ମςετͰɺ֎෦ϞδϡʔϧͷڍಈΛϞοΫ͍ͨ͠ • jest.spyOnͰϞοΫ it('getProducts', async () => {

    jest.spyOn(service, ‘findAll') .mockResolvedValue(PRODUCTS_ARRAY); expect(await controller.getProducts()) .toBe(PRODUCTS_ARRAY); }); @Get() getProducts(): Promise<IProducts[]> { return this.productsService.findAll(); } ςετର৅ ςετίʔυ
  39. JestͰ͸Α͋͘Γ͕ͪͳ͜ͱ… • ୯ମςετͰɺ֎෦ϞδϡʔϧͷڍಈΛϞοΫ͍ͨ͠ • jest.spyOnͰϞοΫ it('getProducts', async () => {

    jest.spyOn(service, ‘findAll') .mockResolvedValue(PRODUCTS_ARRAY); expect(await controller.getProducts()) .toBe(PRODUCTS_ARRAY); }); @Get() getProducts(): Promise<IProducts[]> { return this.productsService.findAll(); } ςετର৅ ςετίʔυ ͍ͭ͜ͷڍಈΛNPDL͍ͨ͠
  40. JestͰ͸Α͋͘Γ͕ͪͳ͜ͱ… • ୯ମςετͰɺ֎෦ϞδϡʔϧͷڍಈΛϞοΫ͍ͨ͠ • jest.spyOnͰϞοΫ it('getProducts', async () => {

    jest.spyOn(service, ‘findAll') .mockResolvedValue(PRODUCTS_ARRAY); expect(await controller.getProducts()) .toBe(PRODUCTS_ARRAY); }); @Get() getProducts(): Promise<IProducts[]> { return this.productsService.findAll(); } ςετର৅ ςετίʔυ ͍ͭ͜ͷڍಈΛNPDL͍ͨ͠ ͍ͭ͜ͷڍಈΛNPDL͢Δ
  41. NestJSͩͱ • moduleʹΑͬͯґଘղܾΛߦ͏ͷͰɺͦ͜ͰmockΛ
 ྲྀ͠ࠐΜͰ͠·͑͹OK describe('Todos Controller', () => { let

    controller: TodosController; let service: TodosService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [{ provide: TodosService, useValue: todoServiceMock, }], controllers: [TodosController], }).compile(); controller = module.get<TodosController>(TodosController); service = module.get<TodosService>(TodosService); }); }); export const todoServiceMock = { findAll: () => TODOS_ARAY, findById: (id: number) => TODOS_ARAY[0], create: (todoDao: TodoDao) => todoDao, }; mockؔ਺ ςετίʔυ
  42. Ұ୴ɺجૅతͳͱ͜Ζ͸ݟͨ!

  43. NestJSͰ։ൃ͢Δ
 ͏·Έ੒෼ͬͯԿ?

  44. ओͳಛ௃ 1. TypeScriptͰͷهड़͕default 2. πʔϧ܈͕๛෋ 3. όοΫΤϯυ (http provider) Λબ୒Մೳ

  45. TypeScriptͰͷهड़͕default • cliʹΑͬͯੜ੒͞Εͨίʔυ͸શͯTypeScriptͰॻ͔Ε͍ͯΔ • ଞͷFrameworkͩͱ(Express…) • ࣗલͰTypeScriptԽ͕ඞཁ • ͱ͸͍͑JavaScriptͰ΋ॻ͚Δ

  46. πʔϧ܈͕๛෋ • ৭ʑͳ΋ͷ͕ఏڙ͞Ε͍ͯΔ • DI, test util, microservice support, WebSockets,

    RxJS • ʮNestJS͸ϓϥοτϑΥʔϜͰ͋Δʯɺͱ͍͏ࢥ૝͔Β
 ͜ͷΑ͏ʹͳ͍ͬͯΔ
  47. όοΫΤϯυ (http provider) Λબ୒Մೳ • package.jsonͱpackage-lock.jsonΛݟͯΈΔ { "name": "todo-app", "dependencies":

    { "express": { "version": "4.17.1", "resolved": “...”, "integrity": “...“, "requires": { ... }, "dependencies": { ... } }, } } { "name": "todo-app", "version": "0.0.1", "scripts": {}, "dependencies": { "@nestjs/common": "^6.7.2", "@nestjs/core": "^6.7.2", "@nestjs/platform-express": "^6.7.2", ... }, "devDependencies": { "@nestjs/cli": "^6.9.0", "@nestjs/schematics": "^6.7.0", "@nestjs/testing": "^6.7.1", "@types/express": "^4.17.1", ... }, }
  48. όοΫΤϯυ (http provider) Λબ୒Մೳ • package.jsonͱpackage-lock.jsonΛݟͯΈΔ { "name": "todo-app", "dependencies":

    { "express": { "version": "4.17.1", "resolved": “...”, "integrity": “...“, "requires": { ... }, "dependencies": { ... } }, } } { "name": "todo-app", "version": "0.0.1", "scripts": {}, "dependencies": { "@nestjs/common": "^6.7.2", "@nestjs/core": "^6.7.2", "@nestjs/platform-express": "^6.7.2", ... }, "devDependencies": { "@nestjs/cli": "^6.9.0", "@nestjs/schematics": "^6.7.0", "@nestjs/testing": "^6.7.1", "@types/express": "^4.17.1", ... }, }
  49. όοΫΤϯυ (http provider) Λબ୒Մೳ • ࣮͸NestJSͷhttp provider͸ࣗલͷ΋ͷͰ͸ͳ͍ • σϑΥϧτ͸Express •

    ଞͷhttp provider΋ར༻͢Δ͜ͱ͕Մೳ • Fastifyͱ͔
 → expressͷ2ഒ΄ͲϕϯνϚʔΫͰྑ͍ੑೳ • ͜ΕʹΑΓ༷ʑͳthird-party plugin͕ར༻Մೳʹ
  50. fastifyʹมߋ • ҎԼͷίϚϯυͰಋೖ
 • main.tsΛҎԼͷΑ͏ʹ͢Δ $ npm i @nestjs/platform-fastify import

    { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { FastifyAdapter } from '@nestjs/platform-fastify'; async function bootstrap() { const app = await NestFactory.create(AppModule, new FastifyAdapter()); await app.listen(3000); } bootstrap();
  51. ͡Ό͋ͳΜͰ

  52. σϑΥϧτ͕expressͳͷ͔…

  53. ৭ʑཧ༝͋Γ • express͕޿͘࢖ΘΕ͍ͯͯmiddleware͕ଟ͍ • Nested routerʹରԠ͍ͯ͠ͳ͍ͨΊɺSub-Domain routing
 ʹରԠ͍ͯ͠ͳ͍ • sub-domain

    routingΛ༗ޮʹ͢Δͱexpressʹ੾ΓସΘΔ
  54. ·ͱΊ • NestJS͸module୯ҐͰͷϩδοΫ෼ׂΛجຊͱ͓ͯ͠Γɺ
 ࠶ར༻ੑͷߴ͍ίʔυ͕ॻ͚Δ • TypeScriptͷσϑΥϧταϙʔτɺDIͷඪ४ఏڙͳͲɺ
 αϙʔτ͕ڧ͍
 (TypeScript൛Spring Bootͱݴͬͯ΋աݴͰ͸ͳ͍)

  55. Reference • Building a platform: NestJS from the ground up

    | Kamil Myśliwiec | jsPoland 2018
 https://youtu.be/f0qzBkAQ3mk • Document of NestJS: https://docs.nestjs.com/
  56. ऴ ੍࡞ɾஶ࡞ ᴸᴸᴸᴸᴸ @jiko_21