なぽろぐ

気ままに感じたことを記事にまとめます。

【NestJSアドベントカレンダー】NestJS & TyoeORMをWebackでつかう!!!【21日目】

NestJS & TypeORM & Webpackで優勝したい!

NestJsはmoduleが膨れ上がっていくと, ts-nodeだと起動がめちゃくちゃ遅くなって開発時にすごいストレスになります。それを解決できるのがHMRで、webpackです

qiita.com

はじめに結論

github.com

entities: getMetadataArgsStorage().tables.map(tbl => tbl.target)をTypeOrmModule.forRootのコンフィグに設定すれば行けます

今回のリポジトリです。

github.com

NestJS & TypeORMの環境を作る

プロジェクトを作って, 必要なものをいれる

nest new sample
cd sample
yarn add @nestjs/typeorm typeorm mysql class-transformer class-validator

ormconfig.jsonをつくる

ormconfig.jsonにしたがってmigrationファイルとかentiiyファイルをtypeorm cliが動きます。 typeorm cliがあるとmigrationファイルを生成したり、entityファイルを生成できるので設定しておきましょう。

{
    "type": "mysql",
    "host": "localhost",
    "port": 3307,
    "username": <user_name>",
    "password": <mysql_password>,
    "database": <database_name>,
    "entities": ["src/entities/*.ts"],
    "migrations": ["src/migrations/*.ts"],
    "synchronize": false, 
    "cli": {
        "migrationsDir": "src/migrations",
        "entitiesDir": "src/entities"
    }
}

cliについてはこちらでより詳細に見れるかと思います。

typeorm.io

app.module.tsにtypeormをimportする

先ほど作ったjsonを元にTypeOrmModuleがデータベースと接続を試みます。

import { Module } from '@nestjs/common';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';

import ormConfig from '../ormconfig.json';

@Module({
  imports: [TypeOrmModule.forRoot(ormConfig as TypeOrmModuleOptions)],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

以下のようなエラーが出るときはdatabase周りの設定が間違っているので見直してみましょう

[TypeOrmModule] Unable to connect to the database. Retrying

一応これでTypeORMが使えるようになってWebpackでビルドの方に行ってもいいのですが、どうせならentityとマイグレーションとCR(UD)を作って、Webpackでビルドしても動くことを確かめましょう。

User Entityを作る

package.jsonのscriptsに

"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js"

を追加しておきましょう。yarn typeorm ...でコマンドを使えるようになって便利です。

Userエンティティを作っていきます。

yarn typeorm entity:create -n User

f:id:Naporitan:20191221182110p:plain

Userには名前を保存するようにしましょう

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id!: number;

    @Column('varchar')
    name: string;
}

エンティティ作成おわり

Migrationを作る

yarn migration:generate -n CreateUserTable

これだけでentities配下にあるファイルとデータベースの差分を計差してtimestamp-CreateUserTableという名前でファイルが作られます。 f:id:Naporitan:20191221182500p:plain

これをデータベースに反映させたい時は以下のコマンドでできます

yarn migration:run

データベースにはmigrationsテーブルが勝手に作られそこでどこまでマイグレーションしたかを保存しているみたいです。 f:id:Naporitan:20191221182642p:plain

UserCR(UD)をつくる

nest g mo user
nest g s user
nest g co user
  • user.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from '../entities/User';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}
  • user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '../entities/User';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dot/createUser.dto';

@Injectable()
export class UserService {
    constructor(@InjectRepository(User) private readonly userRepository: Repository<User>) {}

    async findById(id: number) {
        return await this.userRepository.findOne(id);
    }

    async create(createUserDto: CreateUserDto) {
        return this.userRepository.save(createUserDto);
    }
}
  • user.controller.ts
import { Controller, Get, Param, Post, UsePipes, ValidationPipe, Body } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dot/createUser.dto';

@Controller('user')
export class UserController {
    constructor(private readonly userService: UserService) {}

    @Get(':id')
    async findUserById(@Param('id') id: string) {
        return this.userService.findById(parseInt(id, 10));
    }

    @Post()
    @UsePipes(new ValidationPipe())
    async create(@Body() createUserDto: CreateUserDto) {
        return this.userService.create(createUserDto);
    }
}
  • src/user/createUserDto.ts
import { IsString, IsNotEmpty } from 'class-validator';

export abstract class CreateUserDto {
    @IsString()
    @IsNotEmpty()
    name: string;
}

実行する

これで動けばNestJSとTypeORMの設定は終わりです。

yarn ts-node src/main.ts

f:id:Naporitan:20191221191304g:plain

WebpackでNestJSをビルドする

without cliをやっていきます docs.nestjs.com

必要なライブラリをインストールする

yarn add -D webpack-node-externals webpack webpack-cli webpack-node-externals ts-loader

webpackでHMRをやる

const webpack = require("webpack");
const path = require("path");
const nodeExternals = require("webpack-node-externals");

module.exports = {
  entry: ["webpack/hot/poll?100", "./src/main.ts"],
  target: "node",
  externals: [
    nodeExternals({
      whitelist: ["webpack/hot/poll?100"],
    }),
  ],
  module: {
    rules: [
      {
        test: /.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
  output: {
    path: path.join(__dirname, "dist"),
    filename: "server.js",
  },
};
  • main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

declare const module: any;

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

これをしてyarn webpack --config webpack.cofig.jsしてビルドできれば成功なのですが、できません。

f:id:Naporitan:20191221190637p:plain

こうなると思います。これはormconfig.jsonのentitiesに書いてあるファイルをTypeORMModuleが読みに行こうとするが、commonjsでないため起動できないという感じになります。

これをどう解決するかが、本題なのですが長くなりました。すみません。

答えはこちらです。

github.com

app.modules.tsのTypeOrmModuleでconfigを読み込むところを以下のように変更してください。

import { Module } from '@nestjs/common';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';

import { getMetadataArgsStorage } from 'typeorm';
import ormConfig from '../ormconfig.json';

const { cli, migrations, ...typeOrmConfig } = {
  ...ormConfig,
  entities: getMetadataArgsStorage().tables.map(tbl => tbl.target), // < --これを設定することで
};

@Module({
  imports: [TypeOrmModule.forRoot(typeOrmConfig as TypeOrmModuleOptions), UserModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

f:id:Naporitan:20191221191539g:plain

できた

おわりに

自分の使っているプロジェクトでやってみたんですけど、起動時間が大幅に短縮されています。 f:id:Naporitan:20191221191720g:plain f:id:Naporitan:20191221191713g:plain

明日は @potato4d さんのExpress + TypeORM NestJS 移行です