水無瀬のプログラミング日記

プログラムの勉強した時のメモ

NestJS事始め

はじめに

expressの書き方に疲れてspringを使おうか迷っていた今日このごろ。

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

と謳っているNestJSなるフレームワークがあると聞いたので触ってみる。

TL;DR.

今回作ったサンプル

Install

公式のcliがあるのでサクッとインストール。

$ npm install -g @nestjs/cli

Projectの作成

$ nest new project-name
> npm か yarnどっち使うか聞かれるので好みの方を選択

これでアプリの雛形が作成される。 生成されるファイルは下記の通り。

.
├── README.md
├──node_modules
│   └── 省略
├── nest-cli.json
├── nodemon-debug.json
├── nodemon.json
├── package-lock.json
├── package.json
├── src
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   └── main.ts
├── test
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── tsconfig.build.json
├── tsconfig.json
└── tslint.json

とりあえず起動してみる

@nestjs/cliは後述するが色々ファイルを生成してくれる。
してくれるが、起動してくれる機能は無いので生成されたpackage.jsonに書いてあるnpm scriptを使う。
port3000で起動するので、とりあえずcurlで叩いてみる。

$ npm start
[Nest] 45934   - 06/05/2019, 10:08 PM   [NestFactory] Starting Nest application...
[Nest] 45934   - 06/05/2019, 10:08 PM   [InstanceLoader] AppModule dependencies initialized +19ms
[Nest] 45934   - 06/05/2019, 10:08 PM   [RoutesResolver] AppController {/}: +7ms
[Nest] 45934   - 06/05/2019, 10:08 PM   [RouterExplorer] Mapped {/, GET} route +6ms
[Nest] 45934   - 06/05/2019, 10:08 PM   [NestApplication] Nest application successfully started +3ms
    
$ curl localhost:3000/
> Hello World!

無事動いているようなので手を加えていってみる。

Controller,Serviceを追加してみる

公式サイトのDocumentを参考にしながら、新しいController,Serviceを作ってみる。

# ファイルの生成とmoduleへの追加を自動でしてくれる
$ nest generate controller sample
> CREATE /src/sample/sample.controller.spec.ts (493 bytes)
> CREATE /src/sample/sample.controller.ts (101 bytes)
> UPDATE /src/app.module.ts (397 bytes)

# `g`はgenerateのエイリアス。 sは`service`のエイリアス。(controllerのエイリアスは`co`)
$ nest g s sample 
> CREATE /src/sample/sample.service.spec.ts (460 bytes)
> CREATE /src/sample/sample.service.ts (90 bytes)
> UPDATE /src/app.module.ts (597 bytes)

生成されたファイル

controller

import { Controller } from '@nestjs/common';

@Controller('sample')
export class SampleController {}

service

import { Injectable } from '@nestjs/common';

@Injectable()
export class SampleService {}

Controller,Serviceをちょっと修正

このままでは増えただけなので、ちゃんと中身を書いていく。 とりあえずGET,POST,PUT,DELETE辺りを実装してみる。

Controller

公式サイトのControllerを参考にしながら、書いていく。
色々アノテーションを付けた。今回使って無いのもあるので、詳しくはこちらから。

import {Body, Controller, Delete, Get, HttpCode, Param, Post, Put, Req} from '@nestjs/common';
import {SampleService} from './sample.service';

@Controller('sample')
export class SampleController {
    constructor(private readonly sampleService: SampleService) {}

    @Get()
    public getSample(): string {
        return this.sampleService.getSample();
    }

    /**
     * パスの値を可変にしたい場合、express同様`:変数名`でOK
     * @param params Expressのreq.paramsと同義
     */
    @Get(':id')
    public getSamplePath(@Param() params): string {
        return this.sampleService.getSampleById(params.id);
    }

    /**
     * requestの全量は`@Req()`をつけることで取得できる
     * @param req Expressのreqと同義
     */
    @Post()
    @HttpCode(201)
    public postSample(@Req() req): any {
        return this.sampleService.registerSample(req.body);
    }

    /**
     * Putサンプル
     * @param params パスから受け取る
     * @param body Expressのreq.bodyと同義。bodyだけ取得できる
     */
    @Put(':id')
    public putSample(@Param() params, @Body() body): any {
        return this.sampleService.updateSample(params.id, body);
    }

    /**
     * HttpCodeでレスポンスのステータスコードを指定できる。
     * @param id 直接取得したい場合は`@Param()`の引数に変数名を指定してあげればOK
     */
    @Delete(':id')
    @HttpCode(204)
    public deleteSample(@Param('id') id: string): void {
        this.sampleService.deleteSample(id);
    }
}

Service

こちらはデフォルトとにならってメソッドを増やしただけ。

import { Injectable } from '@nestjs/common';

@Injectable()
export class SampleService {

    public getSample(): string {
        return 'sample service!';
    }

    public getSampleById(id: string): string {
        return `id is ${id}`;
    }

    public registerSample(param: any): any {
        return {message: 'OK', result: param};
    }

    public updateSample(id: string, param: any): any {
        return {message: id, result: param};
    }

    public deleteSample(id: string): void {
        console.log(`${id} is deleted`);
    }
}

叩いてみる

$ curl localhost:3000/sample
> sample service!

$ curl localhost:3000/sample/sampleId
> id is sampleId

$ curl -XPOST -H "Content-Type:application/json" localhost:3000/sample -d '{"id": "sampleId", "title": "sample", "message": "sample message"}' | jq .
>{
>  "message": "OK",
>  "result": {
>  "id": "sampleId",
>  "title": "sample",
>  "message": "sample message"
> }
>}

$ curl -XPUT -H "Content-Type:application/json" localhost:3000/sample/sampleId -d '{"title": "sample", "message": "sample message"}' -v | jq .
>{
>  "message": "sampleId",
>  "result": {
>    "title": "sample",
>    "message": "sample message"
>  }
>}

$ curl -XDELETE localhost:3000/sample/sampleId -v
> ...
>< HTTP/1.1 204 No Content
>< X-Powered-By: Express
>< Date: Wed, 05 Jun 2019 14:49:40 GMT
>< Connection: keep-alive

HTMLを返せるようにする

HTML

まず返すHTMLファイルを作る。
ファイルは下記のような箇所に配置しておく。

.
├── README.md
├── nest-cli.json
├── nodemon-debug.json
├── nodemon.json
├── package-lock.json
├── package.json
├── src
├── test
├── tsconfig.build.json
├── tsconfig.json
├── tslint.json
└── views # ここを追加
    └── index.html # 追加したHTML

main.ts修正

自動生成されたmain.tsに追記する。
これも同じく公式サイトを参考に書いていく。

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import {NestExpressApplication} from '@nestjs/platform-express';
import {join} from 'path';

async function bootstrap() {
    // createの後ろに型を追加
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
    // ファイルが入っているフォルダを指定
  app.useStaticAssets(join(__dirname, '..', 'views'));
  await app.listen(3000);
}
bootstrap();

動かしてみる

npm startで起動した後、localhost:3000にアクセス。 下記の画像の用にHTMLが返ってくればOK。

f:id:minase_mira:20190607224607p:plain

Helmetを導入する

expressでとりあえず入れとけって風潮があるhelmetを導入してみる。
これも公式サイトにあるので参考にしてやる。

helmetインストール

$ npm install --save helmet

main.ts改修

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import {NestExpressApplication} from '@nestjs/platform-express';
import {join} from 'path';
// import追加
import * as helmet from 'helmet';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  app.useStaticAssets(join(__dirname, '..', 'views'));
    // ここでhelmetを使うよう修正
  app.use(helmet());
  await app.listen(3000);
}
bootstrap();

まとめ

今回はnest.jsを使ってみた。
謳っているだけあって、全般的にAngularに似てる感じ。
Controllerの書き方とかはSpringに似てるのかなと思った。
expressよりも書きやすいしデフォルトでtsだし、個人的にはとても良さげ。
多分SSRとかもできるはずなのでいずれ調べたいところ。
これからガンガン使ってみようと思う。

参考資料