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
2.1k
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
130
Array Grouping will soon be arriving at TypeScript
jiko21
0
120
Copying Array Methods arrived at TypeScript
jiko21
1
630
SSRで動的に OGP画像を生成したい! 〜Cloudflare Workersから@vercel/og移行編〜
jiko21
0
130
node:test will replace Jest?
jiko21
0
84
どこでも動かすために… TypeScriptでライブラリ開発の すゝめ
jiko21
2
360
レガシーなフロントエンドをリプレイスする
jiko21
5
1.5k
Deep Dive Into Vue Composition API
jiko21
0
3.2k
Composition API TypeScriptはVue.jsの夢を見るか?
jiko21
1
1.6k
Other Decks in Technology
See All in Technology
SEQUENCE object comparison - db tech showcase 2025 LT2
nori_shinoda
0
130
united airlines ™®️ USA Contact Numbers: Complete 2025 Support Guide
flyunitedhelp
1
300
開発生産性を組織全体の「生産性」へ! 部門間連携の壁を越える実践的ステップ
sudo5in5k
2
7k
B2C&B2B&社内向けサービスを抱える開発組織におけるサービス価値を最大化するイニシアチブ管理
belongadmin
1
6.9k
freeeのアクセシビリティの現在地 / freee's Current Position on Accessibility
ymrl
2
180
Getting to Know Your Legacy (System) with AI-Driven Software Archeology (WeAreDevelopers World Congress 2025)
feststelltaste
1
130
20250707-AI活用の個人差を埋めるチームづくり
shnjtk
4
3.8k
オーティファイ会社紹介資料 / Autify Company Deck
autifyhq
10
130k
Sansanのデータプロダクトマネジメントのアプローチ
sansantech
PRO
0
150
関数型プログラミングで 「脳がバグる」を乗り越える
manabeai
1
190
Oracle Database@Google Cloud:サービス概要のご紹介
oracle4engineer
PRO
0
110
Lufthansa ®️ USA Contact Numbers: Complete 2025 Support Guide
lufthanahelpsupport
0
190
Featured
See All Featured
Keith and Marios Guide to Fast Websites
keithpitt
411
22k
Building Better People: How to give real-time feedback that sticks.
wjessup
367
19k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
A Tale of Four Properties
chriscoyier
160
23k
Practical Orchestrator
shlominoach
189
11k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
Music & Morning Musume
bryan
46
6.6k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
53
2.9k
Being A Developer After 40
akosma
90
590k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Visualization
eitanlees
146
16k
Testing 201, or: Great Expectations
jmmastey
43
7.6k
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