Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
NestJS a progressive web framework
Search
jiko21
February 12, 2020
Technology
3
2k
NestJS a progressive web framework
関西Node学園#9での発表資料です
GitHub:
https://github.com/jiko21/nest-todo-rest
jiko21
February 12, 2020
Tweet
Share
More Decks by jiko21
See All by jiko21
Creating a Next.js-style Framework with Bun and Hono
jiko21
0
91
Array Grouping will soon be arriving at TypeScript
jiko21
0
88
Copying Array Methods arrived at TypeScript
jiko21
1
560
SSRで動的に OGP画像を生成したい! 〜Cloudflare Workersから@vercel/og移行編〜
jiko21
0
110
node:test will replace Jest?
jiko21
0
71
どこでも動かすために… TypeScriptでライブラリ開発の すゝめ
jiko21
2
320
レガシーなフロントエンドをリプレイスする
jiko21
5
1.5k
Deep Dive Into Vue Composition API
jiko21
0
3.2k
Composition API TypeScriptはVue.jsの夢を見るか?
jiko21
1
1.5k
Other Decks in Technology
See All in Technology
滅・サービスクラス🔥 / Destruction Service Class
sinsoku
6
1.6k
モノレポ開発のエラー、誰が見る?Datadog で実現する適切なトリアージとエスカレーション
biwashi
6
800
Culture Deck
optfit
0
420
OpenID Connect for Identity Assurance の概要と翻訳版のご紹介 / 20250219-BizDay17-OIDC4IDA-Intro
oidfj
0
270
スタートアップ1人目QAエンジニアが QAチームを立ち上げ、“個”からチーム、 そして“組織”に成長するまで / How to set up QA team at reiwatravel
mii3king
2
1.5k
データ資産をシームレスに伝達するためのイベント駆動型アーキテクチャ
kakehashi
PRO
2
530
【Developers Summit 2025】プロダクトエンジニアから学ぶ、 ユーザーにより高い価値を届ける技術
niwatakeru
2
1.4k
関東Kaggler会LT: 人狼コンペとLLM量子化について
nejumi
3
580
速くて安いWebサイトを作る
nishiharatsubasa
10
12k
Amazon S3 Tablesと外部分析基盤連携について / Amazon S3 Tables and External Data Analytics Platform
nttcom
0
130
オブザーバビリティの観点でみるAWS / AWS from observability perspective
ymotongpoo
8
1.5k
Goで作って学ぶWebSocket
ryuichi1208
0
160
Featured
See All Featured
We Have a Design System, Now What?
morganepeng
51
7.4k
Unsuck your backbone
ammeep
669
57k
Gamification - CAS2011
davidbonilla
80
5.1k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
114
50k
Building an army of robots
kneath
303
45k
Building a Modern Day E-commerce SEO Strategy
aleyda
38
7.1k
The Cost Of JavaScript in 2023
addyosmani
47
7.3k
The Language of Interfaces
destraynor
156
24k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
Visualization
eitanlees
146
15k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
It's Worth the Effort
3n
184
28k
Transcript
NestJS A progressive web framework #kng9 @jiko_21 (a.k.a Daiki Kojima)
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
Agenda • NestJSͬͯԿ? • ͔ΜͨΜͳ֓ཁ • ҰൠతͳApplicationΛॻ͘ͷʹඞཁͳࣝ • NestJSͷಛ •
·ͱΊ
2018 Warsaw @ PorlandͰ ͜Μͳηογϣϯ͕͋ͬͨ
JavaScript Conference’18 @Warsaw https://youtu.be/f0qzBkAQ3mk
NestJSͱ • Progressive Node.js framework • TypeScriptͰهड़Մೳ • ༷ʑͳπʔϧ͕ఏڙ͞Ε͍ͯΔ
࣮ࡍʹதΛݟͯΈΔ
NestJSͷجૅ • module • ίʔυΛ·ͱΊͯػೳΛཧతͳ୯Ґʹׂ ˠ ࠶ར༻Մೳʹ! • શͯͷNestͷΞϓϦέʔγϣϯʹroot module͕ଘࡏ͠
ΞϓϦέʔγϣϯΛىಈͤ͞Δ (Angularͱಉ༷)
NestJSͷجૅ 3PPUNPEVMF 4VC NPEVMF جຊతʹ͍ΖΜͳͷΛ͜Εʹ ٧ΊࠐΊ͍ͯ͘
moduleΛߏ͢Δͷ
moduleΛߏ͢Δͷ $POUSPMMFST
moduleΛߏ͢Δͷ 1SPWJEFST $POUSPMMFST
moduleΛߏ͢Δͷ 1SPWJEFST $POUSPMMFST *NQPSUT
moduleΛߏ͢Δͷ 1SPWJEFST $POUSPMMFST *NQPSUT &YQPSUT
moduleΛߏ͢Δͷ @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers: [TodosService], controllers: [TodosController], exports: [],
}) export class TodosModule {}
moduleΛߏ͢Δͷ @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers: [TodosService], controllers: [TodosController], exports: [],
}) export class TodosModule {} !.PEVMFʹ ͦΕͧΕΛهೖ͍ͯ͘͠
Controllers
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) } … }
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Λઃఆ
• ϧʔςΟϯάΛ୲͏ • 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) } … }
• ϧʔςΟϯάΛ୲͏ • 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͕ґଘΛղܾ %*ɺޙड़
Providers • providersͷίʔυ NestJSʹΑͬͯΠϯελϯεԽ ͞ΕͯModuleͰڞ༗ (DI) @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers:
[TodosService], controllers: [TodosController], exports: [], }) export class TodosModule {}
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 {}
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Λ ࢦఆ
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
Imports • ͜ͷModuleͰproviderͱ͔͕ඞཁͱ͍ͯ͠ΔModuleΛ ͜͜Ͱॻ͍ͯ͋͛Δ • TypeORMΈ͍ͨͳthird party module • ࣗͰॻ͍ͨΧελϜmodule
Imports @Module({ imports: [TypeOrmModule.forFeature([Todo]),], providers: [TodosService], controllers: [TodosController], exports: [],
}) export class TodosModule {} @Module({ imports: [ TodosModule ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
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͕͑Δ
ׂ͞ΕͨModuleΛ͓͏!
ͱ͍͑…
Serviceͱ͔ͬͯɺ ผͷModuleͰݺͼͨ͘ͳΔΑͶ…
exports • providerͷαϒηοτ • ͜͜ʹॻ͔Ε͍ͯΔͷɺ͜ͷmoduleΛimport͍ͯ͠Δ ଞͷmodule͔Βར༻͕Ͱ͖Δ!
JestͰςετ͕ॻ͚Δ
Jestͱ • FacebookͷJavaScript༻Testing Framework • ଞͷTesting Libraryͱҧ͍ɺAll in one •
ϑϩϯτΤϯυͩͱ… • React, Vue.jsͰΑ͘ΘΕΔ…
ॻ͖ํͷҰྫ describe('TodosService', () => { let service: TodosService; let repository:
Repository<Todo>; beforeEach(async () => { ... }); afterEach(async () => { ... }); it('findAll success', async () => { expect(await service.findAll()).toEqual(TODOS_ARAY); }); });
ॻ͖ํͷҰྫ describe('TodosService', () => { let service: TodosService; let repository:
Repository<Todo>; beforeEach(async () => { ... }); afterEach(async () => { ... }); it('findAll success', async () => { expect(await service.findAll()).toEqual(TODOS_ARAY); }); }); ֤ςετέʔεલʹ࣮ߦ
ॻ͖ํͷҰྫ describe('TodosService', () => { let service: TodosService; let repository:
Repository<Todo>; beforeEach(async () => { ... }); afterEach(async () => { ... }); it('findAll success', async () => { expect(await service.findAll()).toEqual(TODOS_ARAY); }); }); ֤ςετέʔεલʹ࣮ߦ ֤ςετέʔεޙʹ࣮ߦ
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(); } ςετର ςετίʔυ
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͍ͨ͠
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͢Δ
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ؔ ςετίʔυ
Ұ୴ɺجૅతͳͱ͜Ζݟͨ!
NestJSͰ։ൃ͢Δ ͏·ΈͬͯԿ?
ओͳಛ 1. TypeScriptͰͷهड़͕default 2. πʔϧ܈͕๛ 3. όοΫΤϯυ (http provider) ΛબՄೳ
TypeScriptͰͷهड़͕default • cliʹΑͬͯੜ͞ΕͨίʔυશͯTypeScriptͰॻ͔Ε͍ͯΔ • ଞͷFrameworkͩͱ(Express…) • ࣗલͰTypeScriptԽ͕ඞཁ • ͱ͍͑JavaScriptͰॻ͚Δ
πʔϧ܈͕๛ • ৭ʑͳͷ͕ఏڙ͞Ε͍ͯΔ • DI, test util, microservice support, WebSockets,
RxJS • ʮNestJSϓϥοτϑΥʔϜͰ͋Δʯɺͱ͍͏ࢥ͔Β ͜ͷΑ͏ʹͳ͍ͬͯΔ
όοΫΤϯυ (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", ... }, }
όοΫΤϯυ (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", ... }, }
όοΫΤϯυ (http provider) ΛબՄೳ • ࣮NestJSͷhttp providerࣗલͷͷͰͳ͍ • σϑΥϧτExpress •
ଞͷhttp providerར༻͢Δ͜ͱ͕Մೳ • Fastifyͱ͔ → expressͷ2ഒ΄ͲϕϯνϚʔΫͰྑ͍ੑೳ • ͜ΕʹΑΓ༷ʑͳthird-party plugin͕ར༻Մೳʹ
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();
͡Ό͋ͳΜͰ
σϑΥϧτ͕expressͳͷ͔…
৭ʑཧ༝͋Γ • express͕͘ΘΕ͍ͯͯmiddleware͕ଟ͍ • Nested routerʹରԠ͍ͯ͠ͳ͍ͨΊɺSub-Domain routing ʹରԠ͍ͯ͠ͳ͍ • sub-domain
routingΛ༗ޮʹ͢ΔͱexpressʹΓସΘΔ
·ͱΊ • NestJSmodule୯ҐͰͷϩδοΫׂΛجຊͱ͓ͯ͠Γɺ ࠶ར༻ੑͷߴ͍ίʔυ͕ॻ͚Δ • TypeScriptͷσϑΥϧταϙʔτɺDIͷඪ४ఏڙͳͲɺ αϙʔτ͕ڧ͍ (TypeScript൛Spring BootͱݴͬͯաݴͰͳ͍)
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/
ऴ ੍࡞ɾஶ࡞ ᴸᴸᴸᴸᴸ @jiko_21