なぽろぐ

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

【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 移行です

にじめぐりをAirPodsProで聞いてみた感想

にじめぐりをAirPodsProで聞いてみる

セットで売られている骨伝達BluetoothイヤホンではなくAirPodsProで聞いてみたオタクの感想ブログです。外音取り込みでにじめぐりを聞いたらテンションが上がってしまっていてもたってもいられなくなり、文章に綴りました。

AirPodsProを書いました

2週間くらいまってようやくきました。めっちゃ待ちました・・・・

AirPodsProのいいところ

  • ノイキャンがなかなかいい
    • エアコンの音とか車の音、換気扇の音などが消えます。音で気が散る人はおすすめです
  • 外音取り込みがすごい
    • これがにじめぐりとの相性の良さを発揮します。
    • 僕はこの機能が良すぎて常にAirPodsProを装着して人と話しています。(補聴器かな?)
  • ヘッドホン・イヤホンと比べて顔に発生する違和感がない
    • これは人それぞれかと思います。
    • ヘッドホンの頭に乗っかる感じは僕はあまり好きじゃないので完全ワイヤレスのイヤホンが気に入っています。

にじめぐりとは

  • 秋葉原と両国を歩いているとライバーから話しかけてくれる
  • チェックポイントに到達するとライバー同士の会話が聴ける

という感じのGPSと音声を利用したアプリケーションです。

秋葉原に寄ったときに5箇所くらいだけ回ったのですが、本当に最高でした・・・・・・近々時間を空けて再チャレンジする予定です

にじめぐり × AirPodsProの良さ

外音取り込みの精度のよさ。これに尽きると思います。AirPodsProはイヤホンの外にあるマイクで拾った音をスピーカーで再生しているようなものなので(厳密にはどうなっているかは知りませんが)本当に街の雑踏の中にライバー同士の会話が紛れ込んでいるような印象を受けました。

これは少し慣れが必要なのですが、僕は補聴器のように使っていると述べたように1日のほとんどをAirPodsProをつけてすごしています。そうするとつけている感覚が薄れるのです。完全ワイヤレスなのも感覚が薄れる1つの要因になっているでしょう。薄れている状態で、外音取り込みをしながらにじめぐりを聞く!はい、最高。良すぎ!そこにいる。ライバーたちが見えた!!!!!!

最高すぎてイケメンになっちゃいました。

さいこう
さいこう

こんなかんじになれるのでにじめぐり × AirPodsProは相性がいいよって話でした。

AirPodsPro3万だけど常につけてるしライバーが見える(幻覚)ので0円!!!!!!!!!!!!!!

NestJSでwebpack-dev-middlewreを使って気持ちよくなった

NestJSでReactを動かしたい

あること・ないこと

書いてあること

  • 必要なライブラリ
  • NestJSでやるべきこと
  • つまづいたこと

書いてないこと

はじめに

別にSSRとかする話じゃないです。

ディレクトリ構成はこんな感じです。頑張って綺麗にしてるつもりですが、もっといい方法があると思うので教えてください :bow:

  • clientにReactで作っているコンポーネントたち
  • serverにNestJSのnewした時に生成させたものたち

が入っています

.
├── README.md
├── client
├── dist
├── nest-cli.json
├── node_modules
├── package.json
├── public
├── server
├── static
├── test
├── tsconfig.build.json
├── tsconfig.json
├── webpack
├── yarn-error.log
└── yarn.lock

webpack-dev-middlewareなんですけどこいつが何者かっていうと webpack-dev-serverで起動するサーバーをexpressに載せ替えることができる奴なんですね

github.com

api使いたいだけなら普通にdevServerでプロキシすればいいじゃんって話なんですけど、バックエンド起動したらdevServerも起動してくれた方がかっこいいじゃないですか・・・・・・

webpack-dev-middlewareを組み込む

必要なライブラリ yarnなら

yarn add @nestjs/platform-express path
yarn add -D webpack webpack-cli merge @types/webpack @types/webpack-dev-middleware @types/webpack-hot-middleware webpack-dev-middleware webpack-dev-server webpack-hot-middleware

npmなら

npm -i -s @nestjs/platform-express path
npm -i -s --dev webpack webpack-cli merge @types/webpack @types/webpack-dev-middleware @types/webpack-hot-middleware webpack-dev-middleware webpack-dev-server webpack-hot-middleware

やるべきことは簡単で

  1. NestFactory.createのときにNestExpressApplicationを指定する

    • NestのオブジェクトにExpressのuseメソッドを生やすことができます
    • github.com
  2. app.use(webpackMiddleware(....))する

  3. ServerStaticModuleを導入する

くらいでしょうか。あとはnestのサーバ起動して、http://localhost:portに接続すれば、いい感じになるんじゃないですかね。

つまづいたところ

最後に

決していいディレクトリ構造でも、プログラムではないと思います。

ただいい感じにまとまったので個人的には満足しています。

最近の悩み

賞金で炊飯器を買いました

炊飯器をかった

naporitan.hatenablog.com

  • 家が炊飯器ではなく、鍋でご飯を炊く家だったので鍋で炊くのが普通だと思ったんですけど、生きることを頑張ってる人間が生活負荷をあげてはいけないということがわかりました。
  • あと、保温すこすぎない?
    • 20wくらいでいつでもあったかいご飯たべれるのすごい。
      • NAS常時稼働させるくらいなら炊飯器を今すぐ買って白米を保温しろ
  • 米が自動で出来上がる
    • 沸騰を監視してなくていい
    • 米炊いている間買い物に行ける

世間の人々がどうやって生きてるか最近学べている気がします。

早く人間になりたーい

JPHacksAwardDayにて企業賞を2ついただきました

JPHacksAwardDayに行ってきた

JPHacksAwardDayに土曜日行ってました(11/09)

企業賞をいただけました

ありがとうございます!!!

KDDI様と, Fuji Xerox様からいただきました。

写真は会場で撮られたんですけど、僕の手元に今ないのでもらえたら写真を貼ろうと思います。 -> google driveにあげられるらしいのできたら他の人の顔は隠してupします

なんか

去年も今年も変な顔ばっか撮られてるな・・・・・・

youtu.be itjinzai-lab.jp

寝坊して、15時くらいに会場につきました。一緒に出場してくださった他のメンバーすみませんでした!!!!

github actionsのcacheを使ってみてる

GitHubActionsにcacheが出た!!キャッシュだ!!!!!

開発リポジトリ

github.com

実際に使ってみてるやつ

github.com

 - name: Cache node_modules
        uses: actions/cache@preview
        with:
          path: ./apps/main/node_modules <-- cacheしたいnode_modulesのpathをかく
          key: ${{ matrix.os }}-frontend-${{ hashFiles('**/yarn.lock') }} <-- npmだったらpackage-lock.json
          restore-keys: |
            ${{ matrix.os }}-frontend- <- ここは上のkeyと対応させておく

色々な言語でのexampleがあります。困ったらここをみています。

github.com

感想

結構簡単に使える.ただ、フロントエンドとバックエンドでモノレポ状態にしてるときはworking-directoryとcacheするnode_modulesのpath,restore-keyは必ず変えましょう。(ここに気づかずに2日ほど無駄にしました)

restore-keyが同じだとフロントエンドとバックエンド早い方にrestore-keyが書き換えられちゃってぜんぜんcacheの恩恵を受けられないので・・・・

working-directoryとか全部に書いてるけど、jobで設定とかできないのかな? CircleCIはできたような気がしたので(CircleCIをちゃんと触っているわけではないのでわかりませんが・・・・)

なんか

  • firebaseにデプロイするyamlもかきました。なんかもっといい方法があると思うので模索したい

github.com

  • 最近はNestjsを触っています。cloud functionsでも動かせるのでfirebaseで動かして遊んでいます〜

docs.nestjs.com

  • なんかいっぱいcloseされてる!

vue-promisedとcomposition-apiがよさそう

vue-promisedとcomposition-apiを併用してみる

適当にコードだけ貼り付けていた記事がGoogleの検索上位にいたので,これでは情報が少なすぎる・・・・と思い書き直しました.

f:id:Naporitan:20191226023408p:plain:w300

公式の次に出てくる記事がコード貼り付けて動かしてるだけじゃダメでしょ・・・・・・

vue-promisedとは

github.com

PromiseをUI側で処理してあげるのが旨味となります.

created, methodsでasync/awaitすれば解決じゃない?って思うかもしれませんが,Pendingの時はローディングを出して,resolveされたらコンテンツを表示,rejectされたらエラー表示するという時を考えてみます.

そうするとPromiseの状態をUIに伝えるためのFlag変数が必要となり意味の薄い変数がPromiseの数分だけ増えて行きますよね.そこでUI側でPromiseの状態を判別して適切なUIを返してくれるVue-Promiseが嬉しいというわけです.

Flagを持たせて書いてみる

<template>
    <div>
        <div v-if="isPending">
            <p>loading</p>
        </div>
        <div v-else-if="isResolve">
            <p>{{content}}</p>
        </div>
        <div v-else>
            <p>fetch error</p>
        </div>
    </div>
</template>

<script>
import axios from "axios";

export default {
    data() {
        return {
            isPending: false,
            error: "",
            content: {},
        }
    },
    computed: {
        isResolve() {
           return !this.isPending && !error && !!content;
        }
    },
    async created() {
        try {
            const res = await axios.get("http://example.com");
            this.content = res.data;
        } catch (e) {
              this.error = "error";
              console.error(e);
        } finally {
            this.isPending = false;
        }
    }
}
</script>

Vue-Promisedで書いてみる

template層で使うpromise変数にPromiseを渡せばいいです.他はtemplateそうがやってくれるのでロジック側ではPromiseの状態をハンドリングすることなく,resolveされた後のデータ,rejectされた後のデータをどう使うか,だけに絞って書くことができるので非常に簡潔に,そしてわかりやすくかけると思います.

PromiseはUIに任せる.Angulerだと普通みたいですが,僕はVue-Promisedに感激しました.

<template>
  <Promised id="app" :promise="promise">
    <template v-slot:pending>
      <p>loading....</p>
    </template>
    <template v-slot="data">
      <p>{{data}}</p>
    </template>
    <template v-slot:rejected="error">
        {{error}}
    </template>
  </Promised>
</template>

<script>
import { Promised } from "vue-promised";
import axios from "axios";

export default {
  components: { Promised },
  data: {
        return {
            promise: axios.get("http://example.com")
        }
  }
};
</script>

-----------------------------ここまで書き直した------------------------------------

vue-composition-api

vue-composition-api-rfc.netlify.com

<template>
  <Promised id="app" :promise="user">
    <template v-slot:pending>
      <p>loading....</p>
    </template>
    <template v-slot="user">
      <p>{{`${user.firstName}${user.lastName}`}}</p>
    </template>
  </Promised>
</template>

<script lang="ts">
import { createComponent, ref } from "@vue/composition-api";
import { Promised } from "vue-promised";

interface User {
  firstName: string;
  lastName: string;
}
export default createComponent({
  components: { Promised },
  setup() {
    const delayPromise = (time: number) =>
      new Promise(resolve => {
        setTimeout(() => {
          resolve();
        }, time);
      });
    const fetchUser = async (): Promise<User> => {
      await delayPromise(1000);
      return {
        firstName: "naporitan",
        lastName: "napo"
      };
    };
    const user = ref(fetchUser());
    return { user };
  }
});
</script>

Image from Gyazo

promiseをそのまま変数に突っ込んでview側でpromiseを切り分けてくれるのでcreated, mountedで頑張らなくていいのとloadingフラグとか持たなくてよくなるので楽だなぁて感じです。

vue-promisedをjsxで書こうとしたら

JSX 要素型 'Promised' にはコンストラクトも呼び出しシグネチャも含まれていません。

って言われるんだけど、どうしたらいいんだろう。

だからできるはずなんだけどなぁ

Vueの情報収拾をほとんどここでやっています。ラジオ感覚で聞けて助かってる。 uit-inside.linecorp.com