Top View


Author Daiki Urata

NestJSでHot Module Replacementを使った開発環境構築

2019/07/23

環境

  • Node.js: 12.6.0
  • @nestjs/cli: 6.5.0

パッケージのインストール

webpackなどHMRを有効にするためのパッケージをインストールします。

ドキュメントでインストールしているパッケージに加え、webpackの型定義ファイル(@types/webpack-env )もインストールしています。

$ npm install --only=dev webpack webpack-cli webpack-node-externals ts-loader @types/webpack-env

webpack設定

// webpack.config.js
const webpack = require('webpack');
const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
  entry: ['webpack/hot/poll?100', './src/main.ts'],
  watch: true,
  target: 'node',
  externals: [
    nodeExternals({
      whitelist: ['webpack/hot/poll?100'],
    }),
  ],
  module: {
    rules: [
      {
        test: /.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  mode: 'development',
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.WatchIgnorePlugin([/\.js$/, /\.d\.ts$/]),
  ],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'server.js',
  },
};

ドキュメントとの設定に new webpack.WatchIgnorePlugin([/\.js$/, /\.d\.ts$/]) を追加しました。

.js と型定義ファイルの.d.ts は変更を検知する必要がないので外すようにしました。

変更検知の間隔を遅くしたい際は、webpack/hot/poll?100 を100から300に変えるといいと思います。

HMRの有効化

NestJSの開発サーバー側でHMRを有効化する必要があります。

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
  if (module.hot) {
    module.hot.accept();
    module.hot.dispose(() => app.close());
  }
}
bootstrap();

ドキュメントではwebpackの module 変数の型エラーを回避するため、

declare const module: any;

の記述をしていましたが、 @types/webpack-env をインストールしているためここでは必要ありません。

webpackサーバー起動設定

最後にファイルの変更検知をし、HMRを実行するためのwebpackサーバーを起動をnpmコマンドで実行するための設定を行います。

"scripts" オプションにある"start" を更新、 "webpack" コマンドを追加します。

// package.json
{
  "scripts": {
    ...
    "start": "node dist/server",
    "webpack": "webpack --config webpack.config.js",
    ...
}

これでHMR環境が整いました。

webpackサーバー起動

webpackサーバーの起動コマンドを実行して、ファイルの変更検知 + HMRを行います。

$ npm run webpack

その後、ターミナルを別タブなどで開いて、NestJSの開発サーバーを起動します。

$ npm run start

この状態でどれかファイルを変更すると、コンパイルが走りアプリケーションが挙動も変更されていると思います。

npm run start:dev で開発する時よりも明らかにコンパイルが速いはずです。

TypeORMでデータベースに接続できない問題

TypeORMを導入しているプロジェクトでHMR環境で開発している場合、以下のようなデータベースに接続できないエラーが発生します。

EPERM: operation not permitted, scandir ...

これは以下のように、データベースの設定を書いていた場合 __dirname がHMRの時にうまく機能していないのが原因です。

// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { join } from 'path';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'MY_HOST',
      port: 3306,
      username: 'MY_USERNMAE',
      password: 'MY_PASSWORD',
      database: 'MY_DATABASE',
      entities: [join(__dirname, '**/**.entity{.ts,.js}')],
      synchronize: true,
    })
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

この解決方法として以下のようなワークアラウンドができます。

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { getMetadataArgsStorage } from 'typeorm'; // <-変更

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'MY_HOST',
      port: 3306,
      username: 'MY_USERNAME',
      password: 'MY_PASSWORD',
      database: 'MY_DATABASE',
      entities: getMetadataArgsStorage().tables.map(tbl => tbl.target), // <- 変更
      synchronize: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

ファイルからではなくTypeORMの getMetadataArgsStorage() メソッドからテーブルを取得、そこから紐づいているクラスを呼び出して解決しています。

参考: https://github.com/nestjs/nest/issues/755#issuecomment-496793495

最後に

フロントエンドをTypeScriptでの開発を行っていくことで、より堅牢でリッチなアプリケーションを作ることができます。

同様に、バックエンド側も合わせてTypeScriptにすることは処理やデータ構造の共通化をするという意味でもかなり魅力的なことである思っています。

その中でもNestJSはAngularに影響を受けているためか、きっちりした設計でバックエンドアプリの開発を行えるので注目してるのでこれからもNestJSについては書いていこうと思います。

次回は書くと言って書いていなかったTypeORMについての記事を書きたいと思います。

Daiki Urata

Daiki Urata

Twitter X

フロントエンド/モバイルアプリなどを主に開発しています。