$30 off During Our Annual Pro Sale. View Details »

NestJS a progressive web framework

jiko21
February 12, 2020

NestJS a progressive web framework

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

jiko21

February 12, 2020
Tweet

More Decks by jiko21

Other Decks in Technology

Transcript

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

    View Slide

  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

    View Slide

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

    View Slide

  4. 2018೥ Warsaw @ PorlandͰ

    ͜Μͳηογϣϯ͕͋ͬͨ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  8. NestJSͷجૅ
    • module
    • ίʔυΛ·ͱΊͯػೳΛ࿦ཧతͳ୯Ґʹ෼ׂ

    ˠ ࠶ར༻Մೳʹ!
    • શͯͷNestͷΞϓϦέʔγϣϯʹ͸root module͕ଘࡏ͠

    ΞϓϦέʔγϣϯΛىಈͤ͞Δ

    (Angularͱಉ༷)

    View Slide

  9. NestJSͷجૅ
    3PPUNPEVMF
    4VC
    NPEVMF

    جຊతʹ͍ΖΜͳ΋ͷΛ͜Εʹ

    ٧ΊࠐΊ͍ͯ͘

    View Slide

  10. moduleΛߏ੒͢Δ΋ͷ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  17. Controllers

    View Slide

  18. 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)
    }

    }

    View Slide

  19. 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Λઃఆ

    View Slide

  20. • ϧʔςΟϯάΛ୲͏
    • 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)
    }

    }

    View Slide

  21. • ϧʔςΟϯάΛ୲͏
    • 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͕ґଘΛղܾ %*ɺޙड़

    View Slide

  22. Providers
    • providers಺ͷίʔυ͸

    NestJSʹΑͬͯΠϯελϯεԽ

    ͞ΕͯModule಺Ͱڞ༗ (DI)
    @Module({
    imports: [TypeOrmModule.forFeature([Todo]),],
    providers: [TodosService],
    controllers: [TodosController],
    exports: [],
    })
    export class TodosModule {}

    View Slide

  23. 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 {}

    View Slide

  24. 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Λ

    ࢦఆ

    View Slide

  25. 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

    View Slide

  26. Imports
    • ͜ͷModule಺Ͱproviderͱ͔͕ඞཁͱ͍ͯ͠ΔModuleΛ

    ͜͜Ͱॻ͍ͯ͋͛Δ
    • TypeORMΈ͍ͨͳthird party module
    • ࣗ෼Ͱॻ͍ͨΧελϜmodule

    View Slide

  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 {}

    View Slide

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

    View Slide

  29. ෼ׂ͞ΕͨModuleΛ࢖͓͏!

    View Slide

  30. ͱ͸͍͑…

    View Slide

  31. Service૚ͱ͔ͬͯɺ

    ผͷModuleͰݺͼͨ͘ͳΔΑͶ…

    View Slide

  32. exports
    • providerͷαϒηοτ
    • ͜͜ʹॻ͔Ε͍ͯΔ΋ͷ͸ɺ͜ͷmoduleΛimport͍ͯ͠Δ

    ଞͷmodule͔Β௚઀ར༻͕Ͱ͖Δ!

    View Slide

  33. JestͰςετ͕ॻ͚Δ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  38. 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();
    }
    ςετର৅ ςετίʔυ

    View Slide

  39. 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͍ͨ͠

    View Slide

  40. 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͢Δ

    View Slide

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

    View Slide

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

    View Slide

  43. NestJSͰ։ൃ͢Δ

    ͏·Έ੒෼ͬͯԿ?

    View Slide

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

    View Slide

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

    View Slide

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

    ͜ͷΑ͏ʹͳ͍ͬͯΔ

    View Slide

  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",
    ...
    },
    }

    View Slide

  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",
    ...
    },
    }

    View Slide

  49. όοΫΤϯυ (http provider) Λબ୒Մೳ
    • ࣮͸NestJSͷhttp provider͸ࣗલͷ΋ͷͰ͸ͳ͍
    • σϑΥϧτ͸Express
    • ଞͷhttp provider΋ར༻͢Δ͜ͱ͕Մೳ
    • Fastifyͱ͔

    → expressͷ2ഒ΄ͲϕϯνϚʔΫͰྑ͍ੑೳ
    • ͜ΕʹΑΓ༷ʑͳthird-party plugin͕ར༻Մೳʹ

    View Slide

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

    View Slide

  51. ͡Ό͋ͳΜͰ

    View Slide

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

    View Slide

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

    ʹରԠ͍ͯ͠ͳ͍
    • sub-domain routingΛ༗ޮʹ͢Δͱexpressʹ੾ΓସΘΔ

    View Slide

  54. ·ͱΊ
    • NestJS͸module୯ҐͰͷϩδοΫ෼ׂΛجຊͱ͓ͯ͠Γɺ

    ࠶ར༻ੑͷߴ͍ίʔυ͕ॻ͚Δ
    • TypeScriptͷσϑΥϧταϙʔτɺDIͷඪ४ఏڙͳͲɺ

    αϙʔτ͕ڧ͍

    (TypeScript൛Spring Bootͱݴͬͯ΋աݴͰ͸ͳ͍)

    View Slide

  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/

    View Slide


  56. ੍࡞ɾஶ࡞
    ᴸᴸᴸᴸᴸ
    @jiko_21

    View Slide