nodejs用フレームワークnestjsのサンプルを触ってみた(1)
公式docs
https://docs.nestjs.com/
早い話がサーバーサイド版Angular(?)
cats-app example
https://github.com/nestjs/nest/tree/master/examples/01-cats-app
起動
git clone https://github.com/nestjs/nest.git cd ./nest/examples/01-cats-app/ npm i npm run start
localhost:3000
にサーバーが立ち上がります。
とりあえず基本的な部分を使ってみる
その前に cats.controller.ts の中身を一部変更する。
@Post() // @Roles('admin') async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); }
権限の問題を避けるため @Roles
デコレーターをコメントアウトした。 ユーザー権限をメソッド単位で管理できるのは便利そう。
create() メソッドを動かすためのPOSTリクエストを投げてみる。
##Doraemon を追加 $curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{ "name":"Doraemon","age":12,"breed":"R-01FR001-MKII"}' http://localhost:3000/cats/ ##Doramichan を追加 $curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{ "name":"Doramichan","age":10,"breed":"R-01FR001-MKII"}' http://localhost:3000/cats/
次に findAll() メソッドを動かすためのGETリクエストを投げてみる。
$curl http://localhost:3000/cats/ ##catsが帰ってくる。 {"data":[{"name":"Doraemon","age":12,"breed":"R-01FR001-MKII"},{"name":"Doramichan","age":10,"breed":"R-01FR001-MKII"}]}
こんな感じの簡単なHTTPサーバーになっていることがわかる。
構成
/nest/examples/01-cats-app/src$ tree . ├── app.module.ts ├── cats │ ├── cats.controller.spec.ts │ ├── cats.controller.ts ★ │ ├── cats.module.ts │ ├── cats.service.ts ★ │ ├── cats.service.spec.ts ★(作成) │ ├── dto │ │ └── create-cat.dto.ts │ └── interfaces │ └── cat.interface.ts (略) └── main.ts
★...とりあえず今回見てみたところ
コントローラー
クライアントからリクエストを受けてレスポンスを返すクラス。API と処理を記述。
cats/cats.controller.ts
(略) @Controller('cats') @UseGuards(RolesGuard) @UseInterceptors(LoggingInterceptor, TransformInterceptor) export class CatsController { constructor(private readonly catsService: CatsService) {} @Post() // @Roles('admin') async create(@Body() createCatDto: CreateCatDto) { return this.catsService.create(createCatDto); } @Get() async findAll(): Promise<Cat[]> { return await this.catsService.findAll(); } @Get(':name') async findOne(@Param() params):Promise<Cat> { return await this.catsService.findOne(params.name); } }
constructor で catsService
を消費しているあたり Angular でいう *component.ts
でDIしている感じ。
(参考:https://angular.io/guide/dependency-injection)
最後にGet http://localhost:3000/cats/:name
を追加して、猫を名前で検索できるようにした。
@Param()
は expressでいうところの req.params
で、 params.name のようにプロパティにアクセスできる。
コンポーネント
コントローラー(や他のコンポーネント)にDIされるもの。サービスのコンポーネントにはDBとやりとりするためのSQLやORMを書く。
cat.service.ts
(略) /* 追加 */ import { HttpException } from "@nestjs/core"; @Component() export class CatsService { private readonly cats: Cat[] = []; create(cat: Cat) { this.cats.push(cat); return {"message":"success"}; } findAll(): Cat[] { return this.cats; } findOne(name: String): Cat { let cat: Cat = this.cats.find(cat => cat.name == name); if (!cat) { throw new HttpException('Specified cat does not exist !', HttpStatus.BAD_REQUEST); } else { return cat; } } }
テスト
cats.service
に対するunitテストを書いた。
cat.service.spec.ts
import { Test } from '@nestjs/testing';; import { CatsService } from './cats.service'; import { Cat } from './interfaces/cat.interface'; import { CreateCatDto } from './dto/create-cat.dto'; describe('CatsService', () => { let catsService: CatsService; beforeEach(async () => { const module = await Test.createTestingModule({ components: [CatsService], }).compile(); catsService = module.get<CatsService>(CatsService); }); describe('create Test', () => { it('should return a success message', async () => { let cat: Cat = { "name": "Doraemon", "age": 12, "breed": "R-01FR001-MKII" }; let result = { "message": "success" }; expect(await catsService.create(cat)).toEqual(result); }); }); describe('findAll Test', () => { it('should return an array of cats', async () => { let cat1, cat2: Cat; cat1 = { "name": "Doraemon", "age": 12, "breed": "R-01FR001-MKII" }; cat2 = { "name": "Doramichan", "age": 10, "breed": "R-01FR001-MKII" }; let result = [cat1,cat2]; await catsService.create(cat1); await catsService.create(cat2); expect(await catsService.findAll()).toEqual(result); }); }); describe('findOne Test', () => { it('should retrive a specified cat from cats array', async () => { let cat1, cat2: Cat; cat1 = { "name": "Doraemon", "age": 12, "breed": "R-01FR001-MKII" }; cat2 = { "name": "Doramichan", "age": 10, "breed": "R-01FR001-MKII" }; await catsService.create(cat1); await catsService.create(cat2); expect(await catsService.findOne("Doraemon")).toEqual(cat1); expect(await catsService.findOne("Doramichan")).toEqual(cat2); }); it('should return an error object when incoming name does not exist in cats array', async () => { let cat1, cat2: Cat; cat1 = { "name": "Doraemon", "age": 12, "breed": "R-01FR001-MKII" }; cat2 = { "name": "Doramichan", "age": 10, "breed": "R-01FR001-MKII" }; await catsService.create(cat1); await catsService.create(cat2); expect( () => { catsService.findOne("mi-chan") }).toThrow(); }); }); });
expect(FUNCTION).toThrow();
expectの引数はfunctionなので、無名関数の形で渡す必要がある。
(参考:https://ajsblackbelt.wordpress.com/2014/05/18/jasmine-tests-expect-tothrow/)
最後に作った猫検索を試してみる。
## Doraemon 取得 $curl http://localhost:3000/cats/Doraemon/ {"data":{"name":"Doraemon","age":12,"breed":"R-01FR001-MKII"}} ## Doramichan 取得 $ curl http://localhost:3000curl http://localhost:3000/cats/Doramichan/ {"data":{"name":"Doramichan","age":10,"breed":"R-01FR001-MKII"}} ## mi-chan 取得 $ curl http://localhost:3000/cats/mi-chan/ {"statusCode":400,"message":"Specified cat does not exist !"}
感想
nodejs + express から typescript に移行するのが中々難しいなと思っていたので、nestjsのようなフレームワークの存在は非常にありがたいです。
様々な機能があるみたいなので、今後も使ってみようと思います。