Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Agenda • NestJSͬͯԿ? • ͔ΜͨΜͳ֓ཁ • ҰൠతͳApplicationΛॻ͘ͷʹඞཁͳ஌ࣝ • NestJSͷಛ௃ • ·ͱΊ

Slide 4

Slide 4 text

2018೥ Warsaw @ PorlandͰ
 ͜Μͳηογϣϯ͕͋ͬͨ

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

࣮ࡍʹத਎ΛݟͯΈΔ

Slide 8

Slide 8 text

NestJSͷجૅ • module • ίʔυΛ·ͱΊͯػೳΛ࿦ཧతͳ୯Ґʹ෼ׂ
 ˠ ࠶ར༻Մೳʹ! • શͯͷNestͷΞϓϦέʔγϣϯʹ͸root module͕ଘࡏ͠
 ΞϓϦέʔγϣϯΛىಈͤ͞Δ
 (Angularͱಉ༷)

Slide 9

Slide 9 text

NestJSͷجૅ 3PPUNPEVMF 4VC NPEVMF
 جຊతʹ͍ΖΜͳ΋ͷΛ͜Εʹ
 ٧ΊࠐΊ͍ͯ͘

Slide 10

Slide 10 text

moduleΛߏ੒͢Δ΋ͷ

Slide 11

Slide 11 text

moduleΛߏ੒͢Δ΋ͷ $POUSPMMFST

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

moduleΛߏ੒͢Δ΋ͷ @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers: [TodosService], controllers: [TodosController], exports: [], }) export class TodosModule {} !.PEVMF಺ʹ ͦΕͧΕΛهೖ͍ͯ͘͠

Slide 17

Slide 17 text

Controllers

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

• ϧʔςΟϯάΛ୲͏ • pathͱmethod͕
 ରԠ Controllers @Controller('todos') export class TodosController { constructor(private readonly todosService: TodosService) {} @Get() getTodos(): Promise { return this.todosService.findAll(); } @Get(':id') getTodo(@Param('id', new ParseIntPipe()) id: number): Promise { return this.todosService.findById(id); } @Post('') createTodo(@Body() todoDao: TodoDao): Promise { return this.todosService.create(todoDao) } … } DPOTUSVDUPSʹґଘ͢Δ0CKFDUΛ ॻ͍͓ͯ͘ͱ.PEVMF͕ґଘΛղܾ %*ɺޙड़ 


Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Providers @Injectable() export class TodosService { constructor(@InjectRepository(Todo) todoRepo: Repository) {} } @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

Slide 26

Slide 26 text

Imports • ͜ͷModule಺Ͱproviderͱ͔͕ඞཁͱ͍ͯ͠ΔModuleΛ
 ͜͜Ͱॻ͍ͯ͋͛Δ • TypeORMΈ͍ͨͳthird party module • ࣗ෼Ͱॻ͍ͨΧελϜmodule

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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͕࢖͑Δ

Slide 29

Slide 29 text

෼ׂ͞ΕͨModuleΛ࢖͓͏!

Slide 30

Slide 30 text

ͱ͸͍͑…

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

JestͰςετ͕ॻ͚Δ

Slide 34

Slide 34 text

Jestͱ͸ • Facebook੡ͷJavaScript༻Testing Framework • ଞͷTesting Libraryͱҧ͍ɺAll in one • ϑϩϯτΤϯυͩͱ… • React, Vue.js౳ͰΑ͘࢖ΘΕΔ…

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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); service = module.get(TodosService); }); }); export const todoServiceMock = { findAll: () => TODOS_ARAY, findById: (id: number) => TODOS_ARAY[0], create: (todoDao: TodoDao) => todoDao, }; mockؔ਺ ςετίʔυ

Slide 42

Slide 42 text

Ұ୴ɺجૅతͳͱ͜Ζ͸ݟͨ!

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

πʔϧ܈͕๛෋ • ৭ʑͳ΋ͷ͕ఏڙ͞Ε͍ͯΔ • DI, test util, microservice support, WebSockets, RxJS • ʮNestJS͸ϓϥοτϑΥʔϜͰ͋Δʯɺͱ͍͏ࢥ૝͔Β
 ͜ͷΑ͏ʹͳ͍ͬͯΔ

Slide 47

Slide 47 text

όοΫΤϯυ (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", ... }, }

Slide 48

Slide 48 text

όοΫΤϯυ (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", ... }, }

Slide 49

Slide 49 text

όοΫΤϯυ (http provider) Λબ୒Մೳ • ࣮͸NestJSͷhttp provider͸ࣗલͷ΋ͷͰ͸ͳ͍ • σϑΥϧτ͸Express • ଞͷhttp provider΋ར༻͢Δ͜ͱ͕Մೳ • Fastifyͱ͔
 → expressͷ2ഒ΄ͲϕϯνϚʔΫͰྑ͍ੑೳ • ͜ΕʹΑΓ༷ʑͳthird-party plugin͕ར༻Մೳʹ

Slide 50

Slide 50 text

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();

Slide 51

Slide 51 text

͡Ό͋ͳΜͰ

Slide 52

Slide 52 text

σϑΥϧτ͕expressͳͷ͔…

Slide 53

Slide 53 text

৭ʑཧ༝͋Γ • express͕޿͘࢖ΘΕ͍ͯͯmiddleware͕ଟ͍ • Nested routerʹରԠ͍ͯ͠ͳ͍ͨΊɺSub-Domain routing
 ʹରԠ͍ͯ͠ͳ͍ • sub-domain routingΛ༗ޮʹ͢Δͱexpressʹ੾ΓସΘΔ

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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/

Slide 56

Slide 56 text

ऴ ੍࡞ɾஶ࡞ ᴸᴸᴸᴸᴸ @jiko_21