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
73
Array Grouping will soon be arriving at TypeScript
jiko21
0
65
Copying Array Methods arrived at TypeScript
jiko21
1
520
SSRで動的に OGP画像を生成したい! 〜Cloudflare Workersから@vercel/og移行編〜
jiko21
0
110
node:test will replace Jest?
jiko21
0
67
どこでも動かすために… TypeScriptでライブラリ開発の すゝめ
jiko21
2
290
レガシーなフロントエンドをリプレイスする
jiko21
5
1.5k
Deep Dive Into Vue Composition API
jiko21
0
3.2k
Composition API TypeScriptはVue.jsの夢を見るか?
jiko21
1
1.1k
Other Decks in Technology
See All in Technology
IaC運用を楽にするためにCDK Pipelinesを導入したけど、思い通りにいかなかった話
smt7174
1
110
プロダクトチームへのSystem Risk Records導入・運用事例の紹介/Introduction and Case Studies on Implementing and Operating System Risk Records for Product Teams
taddy_919
1
170
マネジメント視点でのre:Invent参加 ~もしCEOがre:Inventに行ったら~
kojiasai
0
460
話題のGraphRAG、その可能性と課題を理解する
hide212131
4
1.5k
一休.comレストランにおけるRustの活用
kymmt90
3
580
Jr. Championsになって、強く連携しながらAWSをもっと使いたい!~AWSに対する期待と行動~
amixedcolor
0
190
Amazon_CloudWatch_ログ異常検出_導入ガイド
tsujiba
4
1.6k
GitHub Universe: Evaluating RAG apps in GitHub Actions
pamelafox
0
170
なんで、私がAWS Heroに!? 〜社外の広い世界に一歩踏み出そう〜
minorun365
PRO
6
1.1k
30万人が利用するチャットをFirebase Realtime DatabaseからActionCableへ移行する方法
ryosk7
5
350
omakaseしないための.rubocop.yml のつくりかた / How to Build Your .rubocop.yml to Avoid Omakase #kaigionrails
linkers_tech
3
730
事業者間調整の行間を読む 調整の具体事例
sugiim
0
1.4k
Featured
See All Featured
XXLCSS - How to scale CSS and keep your sanity
sugarenia
246
1.3M
Fireside Chat
paigeccino
32
3k
VelocityConf: Rendering Performance Case Studies
addyosmani
325
24k
How To Stay Up To Date on Web Technology
chriscoyier
788
250k
A Modern Web Designer's Workflow
chriscoyier
692
190k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
14
1.9k
We Have a Design System, Now What?
morganepeng
50
7.2k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
27
1.9k
Thoughts on Productivity
jonyablonski
67
4.3k
How STYLIGHT went responsive
nonsquared
95
5.2k
5 minutes of I Can Smell Your CMS
philhawksworth
202
19k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
37
1.8k
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