学生ではなくなりました
SOELU株式会社に就職しました
やってること
Reactでこんな画面作ったりしてます.以下の2つは全て僕が実装したものです.
おわりに
コロナの外出自粛ムードが終わったらご飯でも行きましょう
欲しいものリストです.よければなにか・・・・!! Amazon 欲しいものリスト
aspidaとNestJSでHTTPClientにも型定義しながら開発するぞ!!!!!!!
aspidaとNestJSを使って気持ちがいい開発がしたい!したくない?
今回もお試しリポジトリが存在します.NestJS x aspida x Reactの環境で作っています.是非お試しください
なにするの?
NestJSはにはSwaggerModuleがありOpenAPIでしゃべることが可能になります.また, aspidaにはopenapi2aspidaというサブライブラリが存在します.この2つの組み合わせによりNestJS -> OpenAPI -> aspidaができるようになりNestJSでControllerを書くと自動的にHTTPClientの型定義が生成されるという感じになります.バックエンドを書くとフロントエンドに必要なコードが生成されるのは気持ちがいいですね.
OpenAPI経由でaspidaの設定ファイルが吐かれるのでNestJSに限らずいろいろは場面で使えるかと思います.今回はその一例としてNestJSを利用しています.
aspidaとは
HTTPClientに型をつけてくれるライブラリです.GETクエリ, Postパラメータはもちろん, Formデータにも型がつくらしい. しかもURIも保管してくれたりします.
使用感はこんな感じです
今回は /api/user/
に問い合わせるとUser
オブジェクトが返ってくるようなapiを作りました.
導入方法
yarn add @aspida/axios axios
初期設定だと<root>/apis/
以下にエンドポイントと同じ名前のディレクトリを切って設定ファイルを置いていくことになります.この設定ファイルを読んで<root>/apis/$api.ts
が作成されます.
aspidaのビルド設定ファイルは公式を見てください aspida/packages/aspida at master · aspidajs/aspida · GitHub
例えば/api/user
というエンドポイントが生えていたとすると,次のようにファイルを作って設定を書けばおkです
export interface Methods { get: { resBody: Types.User } }
作成後に
yarn aspida --build
で<root>/apis/$api.ts
が作られ型定義されたHTTPClientを使用することができます.
aspidaの設定ファイルは必ずHTTPクライアントをラップする必要があるので,実際使用するときはこのようなutil関数を作っておくと楽だと思います.
import client from "apis/$api"; import aspida from "@aspida/axios"; export const api = client(aspida());
openapi2aspida
OpenAPI形式のjsonをHTTP通信形式で受け取り,aspidaの設定ファイルを自動で作ってくれます.めちゃくちゃ便利.
ルートにaspida.config.json
を作っておき
module.exports = { input: "apis", // outputディレクトリ, openapi: { inputFile: "http://localhost:3000/swagger-json" } }
でおkです.サーバを起動した状態で, openapi2aspida --build
でaspidaの設定ファイルが全て自動生成されます.便利すぎる・・・・
2回目移行ビルドするときはoutputディレクトリ丸ごと消しておく必要があります.
fatal: destination path 'apis' already exists and is not an empty directory.
NestJSについて
Node.jsにおいてバックエンドサーバといえば, Express一強みたいなところがありましたが,最近伸びているバックエンドフレームワークです.Angulerに近い書き心地で様々な機能を後からバシバシ入れることができるプログレッシブフレームワークになっています.Star数も22.5kとかなり多くなってきていて今注目のフレームワークって感じがしてきますね
今回はNestJSには深く触れず, SwaggerModuleを入れるところから始めていきたいと思います.
2019NestJSのアドベントカレンダーに少し参加したので興味のある人はこちらへ
SwaggerModuleを入れてOpenAPIを喋れるようにする
公式ドキュメントにSwaggerModuleの導入の仕方が書いてあるのでそちらを参考に進めていきます.
必要なモジュールを入れる
yarn add @nestjs/swagger swagger-ui-express
main.tsを次のように書き換える
import { NestFactory } from "@nestjs/core"; import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger"; import { AppModule } from "./app.module"; async function bootstrap() { const app = await NestFactory.create(AppModule); app.setGlobalPrefix("api"); // frontendのrenderを/においているのでNestJSで定義される全てのControllerをapi/xxxにしておく // 公式とほとんど同じ const options = new DocumentBuilder() .setTitle("API description") .setVersion("1.0") .addServer("http://localhost:3000/") .build(); const document = SwaggerModule.createDocument(app, options); SwaggerModule.setup("swagger", app, document); await app.listen(3000); } bootstrap();
ここで現在これは修正されていますaddServer
をやっておかないとopenapi2aspida
が実行できないので気をつけましょう.
この行で落ちます
const options = new DocumentBuilder() .setTitle("API description") .setVersion("1.0") .addServer("http://localhost:3000/") .build();
これだけでNestJSがOpenAPIを喋れるようになりました.
yarn start:dev
をやってから
特に何もしていなければhttp://localhost:3000/swaggerでこのような画面を見れると思います.
また,http://localhost:3000/swagger-jsonでOpenAPIを喋っているのがJSON形式でわかります
なんの型が返ってくるかを明示するためにapp.controller.ts
を次のように書き換えます
import { Controller, Get, Post, Body } from "@nestjs/common"; import { AppService } from "./app.service"; import { ApiTags, ApiCreatedResponse } from "@nestjs/swagger"; @ApiTags("AppController") @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() @ApiCreatedResponse({ type: String }) // ここでstringが返ってくることを書いておく getHello(): string { return this.appService.getHello(); } }
これでopenapi2aspidaで型を自動生成する準備は万端です.実際にビルドして使用感を確かめてみましょう
NestJSから吐かれるOpenAPIからaspidaの設定ファイルを生成してみる
NestJSを次のコマンドで起動します
yarn start:dev
http://localhost:3000/swagger-jsonが生きてることを確認し,aspida.config.js
に以下の設定が書いてあれば準備はおkです
module.exports = { input: "apis", openapi: { inputFile: "http://localhost:3000/swagger-json" } }
このコマンドを実行すれば自動生成されます
rm -rf apis yarn openapi2aspida --build
たったこれだけで<root>/apis/$api.ts
が生成されました.
すごいですよね!!! あとはこれをフロントエンドのコードで使うだけ!OpenAPIをメンテしていく意味も出てくるし一石二鳥のライブラリだと思います.
まとめ
去年の12月ごろにも一度使用していたのですが,かなりのアップデートがかかっていてびっくりしました. naporitan.hatenablog.com
NestJSとaspidaを使えばすべてTSの世界でwebの世界を渡り歩けるようになりました.今実戦投入するプロジェクトにこの構成をとっていますが,いい感じです.
副作用として,バックエンドでAPIを書き換えるとき影響範囲がTSの型チェックで落ちたところを修正すればよくなるのでAPI変更の際の精神負荷も少し軽減される気もします.
バックエンドでAPIを作成するとすぐ型安全な状態でそのAPIにアクセスできると言うのは圧巻だと思います. aspida流行れ・・・・!!!
DIVでBlurっぽいことをやる(React)
DIVでもInput要素のblur的なことがしたい!
InputElementは別のところを触るとBlurイベントを発火してフォーカスが外れます.
MDNのサイトで動作は確認できるかと思います.
これをDIV Elementでもやりたいなぁと言うのが今回の議題です.
今回はBlur的なことをしたくなったSelectorUIを用意しました.https://naporin0624.github.io/napoblog-assets/#/div-blur
ようなUIを考えます.
方針
セレクターUIはdivの中に存在するようになっていて,次のようなDOM構成になっています.
- ContainerDIV
- ButtonDIV
- MenuDIV
- OptionGroupDIV
- OptionDIV
- OptionDIV
- ...
- OptionGroupDIV
ButtonDIVもOptionGroupDIVもOptionDIV触っても親の要素は常にContainerDIVになるので
こちらのclosest
メソッドを使ってクリックされた要素の親にContainerがあるかどうかでフォーカスがはずれたかどうかを検知するようにしていきます!関係ないところをクリックしてもその親にはContainerはいないのでフォーカスは外れる仕組みです!
実装
こんな感じでできるかと思います!InputElementが自分でblurイベントを発火してくれるところを自分でclosestメソッドを使ってblurイベント相当のものを生成しています.
function Selector() { // closestにSelectorをわたすためにContainerのrefをとります const ref = useRef<HTMLDivElement>(null); useEffect(() => { const handler = (e: Event) => { // Blurを制御したい親Elementのselector(クラス)refからを作る const selector = ref.current?.className .split(" ") .map(s => `.${s}`) .join(); // eはクリックしたところから発火されるイベント // eをclosestで比較し,eの親にrefが含まれるか確認する const blur = !(e.target as HTMLElement).closest(selector || ""); // 親にrefが含まれていなければフォーカスを外す if (blur) setIsFocus(false); }; document.addEventListener("click", handler); return () => document.addEventListener("click", handler); }, []); return ( <Container ref={ref}> <Button></Button> <Menu> <OptionGroup> <Option>hoge</Option> <Option>huga</Option> <Option>nyan</Option> </OptionGroup> </Menu> </Container> ) }
ここなんですが
const selector = ref.current?.className .split(" ") .map(s => `.${s}`) .join();
const selector = [...ref.current.classList].map(s => `.${s}`).join()
みたいなことができたんですけど,TSだと怒られましたw
最後に
今回使ったプログラムはここに置いてあります!必要であれば使ってください. github.com
自作のUI(DIVで作ったやつ)でフォーカス制御してみたいなことがあって困っていろいろ模索した結果こんな感じの解にたどり着きました.MDNじろじろ見る癖が少しづつついてきたかなぁって感想と,CSS筋が前よりついてきた気がします.
もっとこうしたらいいよとかあれば@naporin24690に教えてください〜!
LottieWebでチェックボックスの状態を管理する
Lottieで消えちゃうCheckBox作るぞ!
こういうのを作りたい
https://naporin0624.github.io/napoblog-assets/#/
今回はこちらのチェックボックスアニメーションを使わせていただきました.
コードの全体像
Lottieのマウントにはreact-lottieを使っています.animを直で触りたいのでそこの部分だけ型定義を拡張して使っている感じですね.
index.tsx
import React, { useRef, useEffect } from "react"; import Lottie from "react-lottie"; import checkbox from "./checkbox.json"; import { AnimationItem } from "lottie-web"; interface LottieType extends Lottie { anim: AnimationItem; } interface Props { checked: boolean; } const options = { loop: false, autoplay: false, animationData: checkbox, rendererSettings: { preserveAspectRatio: "xMidYMid slice", }, }; export const CheckBox = (props: Props) => { const { checked } = props; const ref = useRef<LottieType>(null); const prev = useRef<boolean>(); useEffect(() => { // 初回マウントときはレンダリングしない // falseでは行ってきたときにgoToAndPlayされるのを防ぐため if (prev.current === undefined) { prev.current = checked; // 初回マウント かつ チェックボックスが選択されているならフレームの最後からアニメーションを実行する checked && ref.current?.anim.goToAndPlay(ref.current?.anim.getDuration(true), true); return; } if (checked) { ref.current?.anim.goToAndPlay(0, true); } else { ref.current?.anim.setDirection(-1); ref.current?.anim.goToAndPlay(15, true); } }, [checked]); return <Lottie ref={ref} options={options} width={300} />; };
最後に
lottie-webにはここでフレームを止めるみたいなことができないぽいのでこのような措置を取りました.もっといい方法があれば教えてください🙇♂️
初めはこの子
で実装していてチェックされてから消えるまでセットのアニメーションだったために以下のようにしていたのですが
useEffect(() => { ref.current?.anim.playSegments([30, 75], true); ref.current?.anim.stop(); }, []);
なぜかこうなる・・・・・・
消すとうまくいくので明らかにplaySegmentが悪いのはわかっているんですけど,どう直したらいいのかわからなかったので上で紹介したアニメーションを使いました
有識者教えてください!!!!
わかっていること
- destroyの前に動き始めている
- useEffectのreturnにconsole.logをしこませて検証
- playSegmentの上にconsole.logをおいても初期レンダリング以外では反応なし
ぐらいです・・・・・・
react-transition-groupでリストを表示するときはkeyにindexを設定するのはやめよう
リストで表示したものにトランジションを適用したい
こんなやつをやりたかった
なんかこうなる
🤔
コードを比較
styles.ts
については一番下に書いておきます.
変な挙動をするコード
import React, { Fragment, useState, useCallback, useMemo } from "react"; import { Container, AnimationBox, FormContainer, Input, Submit } from "./styled"; export const ListTransition = () => { const [state, setState] = useState<string[]>(["hoge", "huga"]); const [value, setValue] = useState<string>(""); const onAdd = useCallback(() => { if (value.length <= 0) return; setState(ls => [...ls, value]); setValue(""); }, [value]); const onRemove = useCallback((s: string) => setState(l => l.filter(el => el !== s)), []); const disabled = useMemo(() => value.length <= 0, [value]); return ( <Fragment> <Container> {state.map((s, idx) => ( <AnimationBox key={idx} unmountOnExit timeout={800} onClick={() => onRemove(s)}> {s} </AnimationBox> ))} </Container> <FormContainer> <Input value={value} onChange={e => setValue(e.target.value)} /> <Submit onClick={onAdd} disabled={disabled}> Add Item </Submit> </FormContainer> </Fragment> ); };
想定している動きをするコード
import React, { Fragment, useState, useCallback, useMemo } from "react"; import { Container, AnimationBox, FormContainer, Input, Submit } from "./styled"; export const ListTransition = () => { const [state, setState] = useState<string[]>(["hoge", "huga"]); const [value, setValue] = useState<string>(""); const onAdd = useCallback(() => { if (value.length <= 0) return; setState(ls => [...ls, value]); setValue(""); }, [value]); const onRemove = useCallback((s: string) => setState(l => l.filter(el => el !== s)), []); const disabled = useMemo(() => value.length <= 0, [value]); return ( <Fragment> <Container> {state.map(s => ( <AnimationBox key={s} unmountOnExit timeout={800} onClick={() => onRemove(s)}> {s} </AnimationBox> ))} </Container> <FormContainer> <Input value={value} onChange={e => setValue(e.target.value)} /> <Submit onClick={onAdd} disabled={disabled}> Add Item </Submit> </FormContainer> </Fragment> ); };
原因
keyをindexにしていたからでした.keyが変わるので最後のコンポーネントが消えたと認識されてしまってトランジションが最後のコンポーネントにしか効かなかったというオチでした.
styles.ts
import styled from "styled-components"; import { TransitionGroup } from "react-transition-group"; import transition from "styled-transition-group"; export const Container = styled(TransitionGroup)` position: absolute; top: 0px; display: flex; width: 300px; overflow-x: scroll; margin: 12px; `; export const AnimationBox = transition.p` padding: 8px 16px; font-size: 13px; border: solid 1px #c1c1c1; margin: 0 8px; border-radius: 3px; background-color: #c1c1c1; color: white; cursor: pointer; &:enter { opacity: 0; } &:enter-active { opacity: 1; transition: all 0.8s ease-out; } &:exit { opacity: 1; } &:exit-active { opacity: 0; transition: all 0.8s ease-out; } `; export const FormContainer = styled.div` display: flex; margin-top: 56px; `; export const Input = styled.input``; export const Submit = styled.button``;
【NestJSアドベントカレンダー】NestJSとwebpack-dev-middlewareを組み合わせる【23日目】【遅刻】
NestJSにwebpack-dev-middlewareを組み込む
2020-04-09追記
ライブラリとして公開しました
遅刻しました.今日は25日.クリスマスです.名取の配信見てたら24日もいつのまにか終わっていました. 23日目は研究室の実験があり,徹夜でNestJSを書いていたので実質NestJSアドベントカレンダー書いたようなものじゃないですか?許してください・・・・・・・
本日のリポジトリ
webpack-dev-middlewareって何?
こいつです.こいつは開発時のみ使える良きもの.webpack-dev-serverだと内部のexpressが使われてしまいまサーバーがフロントエンド用と,バックエンド用の2つできてしまいますが,こいつを使えば自分のサーバの上でwebpack-dev-server的なことができてしまうわけです.よいですね.
Usageを見てみましょう
const webpack = require('webpack'); const middleware = require('webpack-dev-middleware'); const compiler = webpack({ // webpack options }); const express = require('express'); const app = express(); app.use( middleware(compiler, { // webpack-dev-middleware options }) ); app.listen(3000, () => console.log('Example app listening on port 3000!'));
シンプルですね.これをNestJSに組み込んでいきます.
NestJS & webpack-dev-middleware
僕 <「webpack-dev-middlewareを使う.」,「本番でもそのまま動くようにする.」両方やらなくちゃいけないのが〇〇の辛いところだな.*〇〇にしたのは思いつかなかったため
webpack-dev-middlewareを使うmoduleを作る
client.module.tsみたいな名前でやりましょう.名前をつけるのが下手なのは目を瞑ってください
nest g mo client
import { Module, DynamicModule, OnModuleInit, Inject } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { CLIENT_MODULE_OPTIONS } from './client-options.constant'; import { ClientOptions } from './client-options.interface'; import { ClientProvider } from './client.provider'; @Module({ providers: [ClientProvider], }) export class ClientModule implements OnModuleInit { constructor( @Inject(CLIENT_MODULE_OPTIONS) private readonly clientOptions: ClientOptions, private readonly httpAdapterHost: HttpAdapterHost, private readonly clientProvider: ClientProvider, ) {} static forRoot(options: ClientOptions): DynamicModule { return { module: ClientModule, providers: [ { provide: CLIENT_MODULE_OPTIONS, useValue: options, }, ], }; } public async onModuleInit() { if (process.env.NODE_ENV !== 'development') return; const httpAdapter = this.httpAdapterHost.httpAdapter; const { webpackConfig } = this.clientOptions; this.clientProvider.register(httpAdapter, webpackConfig); } }
moduleではforRootを使ってDynamicModuleを返すようにします.MongooseModuleとかTypeOrmModuleとかと同じですね.
@Inject(CLIENT_MODULE_OPTIONS) private readonly clientOptions: ClientOptions
これ頭いいなぁって思ったんですけど,forRootで返したproviderから拾ってきています.forRootはstaticmethodなのでonModuleInitに対して変数を渡すことはできないのですがこれで実現しています.
こんなことは自分では考え付かない・・・後述するServeStaticModule
の内部実装がこうなっていたのを真似しました.
続いてProviderを作っていきます. Providerではwebpackのコンフィグをwebpack-dev-middlewareに食わせて,httpAdapter.つまり,NestJSの本体に登録していきます.
import { Injectable } from '@nestjs/common'; import { Configuration, Entry, EntryFunc } from 'webpack'; import webpack from 'webpack'; import { AbstractHttpAdapter } from '@nestjs/core'; import webpackDevMiddleware from 'webpack-dev-middleware'; import webpackHotMiddleware from 'webpack-hot-middleware'; @Injectable() export class ClientProvider { async register(app: AbstractHttpAdapter, config: Configuration) { const compiler = webpack({ ...config, mode: 'development', entry: await this.appendHotMiddlewareToEntry(config.entry), plugins: [...config.plugins, new webpack.HotModuleReplacementPlugin()], }); app.use(webpackDevMiddleware(compiler)); app.use(webpackHotMiddleware(compiler)); } private async appendHotMiddlewareToEntry( entry: string | string[] | Entry | EntryFunc, ) { const hot: string = 'webpack-hot-middleware/client?reload=true&timeout=1000'; let e = entry; e = e instanceof Function ? await e() : e; if (e instanceof Array) return [...(e as string[]), hot]; else if (typeof e === 'string') return [e, hot]; else { for (const key in e) { if (e[key] instanceof Array) e[key] = [...e[key], hot]; else e[key] = [e[key], hot] as string[]; } return e; } } }
どうせならHMRも登録してしまえ!ということでやっています.appendHotHiddlewareToEntry
ではwebpackのentry全てにhot-middlewareを付け加えています.なんかヤバそうな気がする・・・・
ここでやるのはwebpack-dev-middleware周りの設定だけにしています. 特に説明すべきことはないと思いますので次に行きます.
configの型を決めましょう.
configだけくれればそれでええ!
import { Configuration } from 'webpack'; export interface ClientOptions { webpackConfig: Configuration; }
export const CLIENT_MODULE_OPTIONS = 'CLIENT_MODULE_OPTIONS';
moduleを登録する
import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ClientModule } from './client/client.module'; import config from '@/webpack.config.client.js'; import { Configuration } from 'webpack'; @Module({ imports: [ ClientModule.forRoot({ webpackConfig: config as Configuration, }), ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
実行する
やることリスト
- [ ] webpack.config.jsを書く
- [ ] フロントエンドのコードをcliet/App.tsxみたいな感じで作っとく
わからなかったら僕の作ったリポジトリをそのまま使えば大丈夫です
動かしてみる
NODE_ENV=development yarn ts-node src/main.ts
で動くはず
僕のリポジトリを使っている場合は
yarn build:dev yarn start:dev
でも動きます.
プロダクションではwebpack-dev-middlewareを使わずにやる
serve-staticを使うと静的サイトを配信できます.これと今作ったwebpack-dev-middlewareのmoduleをNODE_ENV
の状態で切り替えれば完璧に動くはずです.
moduleを変更する
import { Module, DynamicModule, OnModuleInit, Inject } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import { CLIENT_MODULE_OPTIONS } from './client-options.constant'; import { ClientOptions } from './client-options.interface'; import { ServeStaticModule } from '@nestjs/serve-static'; import { ClientProvider } from './client.provider'; @Module({ providers: [ClientProvider], }) export class ClientModule implements OnModuleInit { constructor( @Inject(CLIENT_MODULE_OPTIONS) private readonly clientOptions: ClientOptions, private readonly httpAdapterHost: HttpAdapterHost, private readonly clientProvider: ClientProvider, ) {} static forRoot(options: ClientOptions): DynamicModule { return process.env.NODE_ENV !== 'development' ? ServeStaticModule.forRoot(options) : { module: ClientModule, providers: [ { provide: CLIENT_MODULE_OPTIONS, useValue: options, }, ], }; } public async onModuleInit() { if (process.env.NODE_ENV !== 'development') return; const httpAdapter = this.httpAdapterHost.httpAdapter; const { webpackConfig } = this.clientOptions; this.clientProvider.register(httpAdapter, webpackConfig); } }
interfaceを変更する
import { ServeStaticModuleOptions } from '@nestjs/serve-static'; import { Configuration } from 'webpack'; export interface ClientOptions extends ServeStaticModuleOptions { webpackConfig: Configuration; }
app.module.tsに変更を反映する
静的サイトはdist/public
にbuildされるように僕のリポジトリでは設定されているのでそのように設定します.
import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; import { ClientModule } from './client/client.module'; import config from '@/webpack.config.client.js'; import { Configuration } from 'webpack'; import { getMetadataArgsStorage } from 'typeorm'; import { UserModule } from './user/user.module'; import ormConfig from '@/ormconfig.json'; import { join } from 'path'; const { cli, migrations, ...typeOrmConfig } = { ...ormConfig, entities: getMetadataArgsStorage().tables.map(tbl => tbl.target), }; @Module({ imports: [ ClientModule.forRoot({ renderPath: '/', rootPath: join(__dirname, 'public'), webpackConfig: config as Configuration, }), TypeOrmModule.forRoot(typeOrmConfig as TypeOrmModuleOptions), UserModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
以上です.これで先ほどと同じように
NODE_ENV=development yarn ts-node src/main.ts
僕のリポジトリを使っている場合は
yarn build:dev yarn start:dev
で動くかと思います.clientもビルドして配信されるかどうか試してみてください.
僕のリポジトリでは
yarn build yarn start:prod
でできると思います.
TypeORMとwebpack-dev-middlewareとaspidaと俺と
Reactで作ったUserのCreateとid検索ができるサンプルアプリを作りました.aspidaで型安全にapiを叩きながらNestJSでapiを実装する.それを開発時はwebpack-dev-middlewareから配信されるReactから叩く,プロダクション時はserve-staticで配信されるReactからapiが叩かれる.なかなか型で守られていていいのではないでしょうか?僕はいいと思って作りました.
動かし方
開発時
docker-compose up --build -d yarn typeorm migration:run yarn build:dev yarn start:dev
それ以外
mysqlはdocker-composeで立ち上げたままで,開発環境と別にしたい時は,ormconfig.jsonをdevとprodでわけるといいでしょう.
yarn build yarn start:prod
こんな画面になるはずです
おわりに
お疲れ様でした.長い記事になってしまいました.きっとDXが上がるはず!と信じてやってみました.なかなか良さそうではないでしょうか?
今回はReactでやっていますが,Vueでもできると思います.Angularはコマンド一発でできるのでこんなことはしなくていいです.
どうでもいいのですが.明日卒論の目次案のゼミがあります.書くことが何もありません・・・・・NestJSの記事貼り付けようかな・・・・
これと関連している記事でTypeORMとwebpackという記事も書いているのでよかったらみてください naporitan.hatenablog.com