【NestJSアドベントカレンダー】NestJS & TyoeORMをWebackでつかう!!!【21日目】
NestJS & TypeORM & Webpackで優勝したい!
NestJsはmoduleが膨れ上がっていくと, ts-nodeだと起動がめちゃくちゃ遅くなって開発時にすごいストレスになります。それを解決できるのがHMRで、webpackです
はじめに結論
entities: getMetadataArgsStorage().tables.map(tbl => tbl.target)
をTypeOrmModule.forRootのコンフィグに設定すれば行けます
今回のリポジトリです。
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についてはこちらでより詳細に見れるかと思います。
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
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
という名前でファイルが作られます。
これをデータベースに反映させたい時は以下のコマンドでできます
yarn migration:run
データベースにはmigrationsテーブルが勝手に作られそこでどこまでマイグレーションしたかを保存しているみたいです。
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
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
してビルドできれば成功なのですが、できません。
こうなると思います。これはormconfig.jsonのentitiesに書いてあるファイルをTypeORMModuleが読みに行こうとするが、commonjsでないため起動できないという感じになります。
これをどう解決するかが、本題なのですが長くなりました。すみません。
答えはこちらです。
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 {}
できた
おわりに
自分の使っているプロジェクトでやってみたんですけど、起動時間が大幅に短縮されています。
明日は @potato4d さんのExpress + TypeORM NestJS 移行です
にじめぐりをAirPodsProで聞いてみた感想
にじめぐりをAirPodsProで聞いてみる
セットで売られている骨伝達BluetoothイヤホンではなくAirPodsProで聞いてみたオタクの感想ブログです。外音取り込みでにじめぐりを聞いたらテンションが上がってしまっていてもたってもいられなくなり、文章に綴りました。
AirPodsProを書いました
2週間くらいまってようやくきました。めっちゃ待ちました・・・・
やあああああああたああああああAirPodsPro届いた!!、!!!!! pic.twitter.com/2rNsUCOR37
— 🍆naporitan🍆@うーねこ団 (@naporin24690) 2019年11月22日
AirPodsProのいいところ
- ノイキャンがなかなかいい
- エアコンの音とか車の音、換気扇の音などが消えます。音で気が散る人はおすすめです
- 外音取り込みがすごい
- これがにじめぐりとの相性の良さを発揮します。
- 僕はこの機能が良すぎて常にAirPodsProを装着して人と話しています。(補聴器かな?)
- ヘッドホン・イヤホンと比べて顔に発生する違和感がない
- これは人それぞれかと思います。
- ヘッドホンの頭に乗っかる感じは僕はあまり好きじゃないので完全ワイヤレスのイヤホンが気に入っています。
にじめぐりとは
- 秋葉原と両国を歩いているとライバーから話しかけてくれる
- チェックポイントに到達するとライバー同士の会話が聴ける
という感じのGPSと音声を利用したアプリケーションです。
秋葉原に寄ったときに5箇所くらいだけ回ったのですが、本当に最高でした・・・・・・近々時間を空けて再チャレンジする予定です
【スマホアプリ「にじめぐり」サービス開始!!】
— にじさんじ公式🌈🕒 (@nijisanji_app) 2019年12月1日
「VtL両国」に出演するメンバーが現実世界に飛び出して、秋葉原・両国の街巡り!
1カ月間限定のスマホアプリ「にじめぐり」いよいよ本日よりサービス開始!
iOS版▽https://t.co/AW1SOaLZDG
Android版▽https://t.co/18feBsfgB5#VtL両国 pic.twitter.com/deE8KKpn1Q
にじめぐり × AirPodsProの良さ
外音取り込みの精度のよさ。これに尽きると思います。AirPodsProはイヤホンの外にあるマイクで拾った音をスピーカーで再生しているようなものなので(厳密にはどうなっているかは知りませんが)本当に街の雑踏の中にライバー同士の会話が紛れ込んでいるような印象を受けました。
これは少し慣れが必要なのですが、僕は補聴器のように使っていると述べたように1日のほとんどをAirPodsProをつけてすごしています。そうするとつけている感覚が薄れるのです。完全ワイヤレスなのも感覚が薄れる1つの要因になっているでしょう。薄れている状態で、外音取り込みをしながらにじめぐりを聞く!はい、最高。良すぎ!そこにいる。ライバーたちが見えた!!!!!!
最高すぎてイケメンになっちゃいました。
こんなかんじになれるのでにじめぐり × AirPodsProは相性がいいよって話でした。
AirPodsPro3万だけど常につけてるしライバーが見える(幻覚)ので0円!!!!!!!!!!!!!!
NestJSでwebpack-dev-middlewreを使って気持ちよくなった
NestJSでReactを動かしたい
あること・ないこと
書いてあること
- 必要なライブラリ
- NestJSでやるべきこと
- つまづいたこと
書いてないこと
- webpackの詳細な設定
- 人それぞれ違うので参考になるかわからないため。
- webpack-dev-serverの説明
はじめに
別に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に載せ替えることができる奴なんですね
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
やるべきことは簡単で
NestFactory.create
のときにNestExpressApplication
を指定する- NestのオブジェクトにExpressのuseメソッドを生やすことができます
- github.com
app.use(webpackMiddleware(....))する
ServerStaticModuleを導入する
くらいでしょうか。あとはnestのサーバ起動して、http://localhost:portに接続すれば、いい感じになるんじゃないですかね。
つまづいたところ
- nestのglobal-prefixに何かしらつけておく、webクライアントと差別化できる何かをprefix煮付けるといい気がします
- nestのportとwebpack-dev-serverのポートを合わせておく
- webpackがjsなので弾かれる.
- tsconfig.jsonで
allowJS: true
にすればいいです
- tsconfig.jsonで
- webpackでoutput.fileにhashを使っている場合
- hmlWebpackPluginを使えば良いかと思います
- ここで注意なのですが、htmlWebpackPluginへ渡す
index.html
のpathはwebpack.config.dev.jsがある場所からのpathではなく、webapck-dev-middlewareを使っているファイルからのpathになります。
最後に
決していいディレクトリ構造でも、プログラムではないと思います。
ただいい感じにまとまったので個人的には満足しています。
NestJSにwebpack-dev-midlleware組み込んでReact動かしてみたけど思ったより心地いい
— 🍆naporitan🍆@うーねこ団 (@naporin24690) 2019年11月19日
全部Nestで動いてくれてることが単に気持ちいいわ
— 🍆naporitan🍆@うーねこ団 (@naporin24690) 2019年11月19日
最近の悩み
最近コンフィグとかこう書いた方が気持ちいいかなーみたいなことばっか考えててちゃんともの作ってないのやばいぞ
— 🍆naporitan🍆@うーねこ団 (@naporin24690) 2019年11月19日
賞金で炊飯器を買いました
炊飯器をかった
一人暮らしして1年ようやく炊飯器を買いました!ハッカソンの賞金で買いました
— 🍆naporitan🍆@うーねこ団 (@naporin24690) 2019年11月12日
今まで鍋で飯盒で炊いてたようやく解放さへる! pic.twitter.com/vcZiRrNLcE
- 家が炊飯器ではなく、鍋でご飯を炊く家だったので鍋で炊くのが普通だと思ったんですけど、生きることを頑張ってる人間が生活負荷をあげてはいけないということがわかりました。
- あと、保温すこすぎない?
- 20wくらいでいつでもあったかいご飯たべれるのすごい。
- NAS常時稼働させるくらいなら炊飯器を今すぐ買って白米を保温しろ
- 20wくらいでいつでもあったかいご飯たべれるのすごい。
- 米が自動で出来上がる
- 沸騰を監視してなくていい
- 米炊いている間買い物に行ける
世間の人々がどうやって生きてるか最近学べている気がします。
早く人間になりたーい
JPHacksAwardDayにて企業賞を2ついただきました
JPHacksAwardDayに行ってきた
JPHacksAwardDayに土曜日行ってました(11/09)
寝てたら入賞しました https://t.co/CyKLzkZp8c pic.twitter.com/du9foONZur
— 🍆naporitan🍆@うーねこ団 (@naporin24690) 2019年11月1日
企業賞をいただけました
ありがとうございます!!!
写真は会場で撮られたんですけど、僕の手元に今ないのでもらえたら写真を貼ろうと思います。 -> google driveにあげられるらしいのできたら他の人の顔は隠してupします
なんか
去年も今年も変な顔ばっか撮られてるな・・・・・・
寝坊して、15時くらいに会場につきました。一緒に出場してくださった他のメンバーすみませんでした!!!!
github actionsのcacheを使ってみてる
GitHubActionsにcacheが出た!!キャッシュだ!!!!!
開発リポジトリ
実際に使ってみてるやつ
- 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があります。困ったらここをみています。
感想
結構簡単に使える.ただ、フロントエンドとバックエンドでモノレポ状態にしてるときはworking-directoryとcacheするnode_modulesのpath,restore-keyは必ず変えましょう。(ここに気づかずに2日ほど無駄にしました)
restore-keyが同じだとフロントエンドとバックエンド早い方にrestore-keyが書き換えられちゃってぜんぜんcacheの恩恵を受けられないので・・・・
working-directoryとか全部に書いてるけど、jobで設定とかできないのかな? CircleCIはできたような気がしたので(CircleCIをちゃんと触っているわけではないのでわかりませんが・・・・)
なんか
- firebaseにデプロイするyamlもかきました。なんかもっといい方法があると思うので模索したい
- 最近はNestjsを触っています。cloud functionsでも動かせるのでfirebaseで動かして遊んでいます〜
- なんかいっぱいcloseされてる!
なんか個人開発してる奴がめちゃめちゃクローンされてるんだが pic.twitter.com/yslwJ7wwsR
— 🍆naporitan🍆@うーねこ団 (@naporin24690) 2019年11月10日
vue-promisedとcomposition-apiがよさそう
vue-promisedとcomposition-apiを併用してみる
適当にコードだけ貼り付けていた記事がGoogleの検索上位にいたので,これでは情報が少なすぎる・・・・と思い書き直しました.
公式の次に出てくる記事がコード貼り付けて動かしてるだけじゃダメでしょ・・・・・・
vue-promisedとは
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>
promiseをそのまま変数に突っ込んでview側でpromiseを切り分けてくれるのでcreated, mountedで頑張らなくていいのとloadingフラグとか持たなくてよくなるので楽だなぁて感じです。
vue-promisedをjsxで書こうとしたら
JSX 要素型 'Promised' にはコンストラクトも呼び出しシグネチャも含まれていません。
って言われるんだけど、どうしたらいいんだろう。
vue-promised内部実装jsxじゃん
— 🍆naporitan🍆@うーねこ団 (@naporin24690) 2019年10月21日
だからできるはずなんだけどなぁ
Vueの情報収拾をほとんどここでやっています。ラジオ感覚で聞けて助かってる。 uit-inside.linecorp.com