GAミント至上主義

Web Monomaniacal Developer.

このブログについて(先頭固定)

【お約束】 投稿内容は個人の見解であり、所属する(していた)組織の公式見解ではありません。

名前:uyamazak(昔いた会社で上司が開発用Linuxサーバーのユーザー名に「yuyamazaki」が長いので勝手に作ってくれた。読み方わからないけどウヤマザク)

高校あたりからWEBサイトをやったり趣味の延長でWEB系を仕事にした感じの人間。

2020年1月から株式会社シニアジョブにジョイン。
2021/1現在、まだ開発者は3人、これからの会社なのでビジネス貢献しつつ、きれいな設計をしたい

アズールレーン@竹敷でモバイルのUI、UX研究中(初嫁ジャベリン)
フレンド&大艦隊メンバー募集してます ID:939524678 @竹敷



GitHub: https://github.com/uyamazak/

これまでの主なプロジェクト

続きを読む

NestJSの型定義をNuxt3で使えるようにエクスポートする

フロント、サーバーともにTypeScriptのWEBアプリケーションにおいて、 NestJS側のEntityやDTO、GraphQLの型をNuxt3側で使えると非常に便利だったのでメモ。

システム全体像にとしてはこの記事参照 uyamazak.hatenablog.com

いろいろ前提

レポジトリはNestJS、Nuxt3は同一で下記のようなディレクトリとします

.
├── types # 共有する型定義置き場
├── front # Nuxt3のディレクトリ
└── server # NestJSのディレクトリ

Nuxt3のバージョンは

    "nuxt": "3.0.0-rc.8",

NestJSのバージョンは ここらへん

    "@nestjs/apollo": "^10.0.16",
    "@nestjs/common": "8.0.9",
    "@nestjs/config": "2.1.0",
    "@nestjs/core": "8.4.7",
    "@nestjs/graphql": "10.0.16",
    "@nestjs/platform-express": "8.4.7",
    "@nestjs/typeorm": "^8.1.4",

Entity、DTOなど型定義の共有

編集時などよくEntityそのままの値を返すことがあり、Nuxt側のfetchやuseAsyncDataなどに指定できると便利です。

POST、PUTなどのリクエスト時はDTOの型が使えると補完や型チェックができて非常に便利です。

そもそもDTOはなんぞやって場合は下記参照。 NestJS の DTO と Validation の基本 - 型定義とデコレータで安全にデータを受け付ける - Qiita https://docs.nestjs.com/openapi/types-and-parameters#types-and-parameters

出力専用のtsconfingを用意します

server/tsconfig.shared.json

{
  "extends": "./tsconfig.json",
  "include": [
    "./src/dto",
    "./src/entity",
    "./src/types.ts",
    "./src/param",
    "./src/class-validator-jp.ts"
  ],
  "compilerOptions": {
    "outDir": "../types"
  }
}

メインのやつをextendsしつつ、includeで必要なところだけを指定、outDirで上記の出力先ディレクトリを指定します。

毎回手打ちは面倒なので出力コマンドを server/package.jsonに用意しておきます。

  "scripts": {
    "export:types": "rimraf ../types && tsc -P tsconfig.shared.json --emitDeclarationOnly",
  },

古いゴミが残らない用に書き出し前にrimrafを使って削除を行っています(NestJSのデフォルトprebuildで使ってたので流用) npm run のスクリプトの中でディレクトリの削除を行う (rimraf) - まくまくNode.jsノート

先程用意した出力用tsconfigを指定し、

型定義だけでいいので

--emitDeclarationOnly

を指定します。 これを指定しないと値も出力されるわけですが、それすなわち使用するfront側でもNestJSやその他サーバー側ライブラリのインストールが必要になってしまうので現実的ではありません。

yarnを使っているのでserverディレクトリで

yarn export:types

で出力されるようになります

index.tsも自動生成する

ファイル数が増えてくると、import時に全部指定するのが面倒なのでディレクトリごとにindex.tsがあると便利です。

自動生成してくれる下記ライブラリをインストールして使っています。

create-ts-index - npm search

yarn add -D create-ts-index

して package.jsonのscriptsに下記のようなコマンドを追加して使っています。 全フォルダにはいらないので、ファイル数が多い必要な場所だけ指定しています。

    "cti": "cti create src/dto src/entity -b -s=1 -i=spec.ts",

これで

import { FugaEntiry } from '~/entity/fuga-entity.entity'

のようなfromを

import { FugaEntiry } from '~/entity/'

に省略できるようになります。

Entityの型とレスポンスの型が違う場合

TypeORMでLazy Relationsを使っているとEntityのプロパティの型はそのままPromiseになってしまいますが、 レスポンスとして返す場合はJSONですのでもちろん解決した値を返すと思います。

typeorm/eager-and-lazy-relations.md at master · typeorm/typeorm · GitHub

その場合、あまりきれいではありませんが、上書きして新しい型を作って使うようにしています。 今Nuxt3側で出力してるけど、よく考えるとサーバー側で出力した方が変更しやすくていいかもしれない。

import {
  HogeEntity,
  LazyItem
} from '~/../types/entity'

export interface HogeEntityResponse extends Omit<HogeEntity, 'lazyItems'> {
  lazyItems: LazyItem[] // 元は Promise<LazyItem[]>
}

GraphQLの型を出力する

こちらを参考にCode firstとして使っています。

https://docs.nestjs.com/graphql/quick-start

GraphQLの型を書き出してくれるこいつをインストールします @graphql-codegen/cli - npm

yarn add -D @graphql-codegen/cli

設定ファイルを追加します

server/codegen.yml

schema: ./src/schema.graphql
generates:
  ../types/graphql-types.ts:
    plugins:
      - typescript

出力先 (../types/graphql-types.ts) とpluginsでtypescriptを指定するだけで使えました。

これもpackage.jsonのscriptsに追加

    "graphql-codegen": "graphql-codegen",

上記の型定義とGraphQLの型定義は同時にやってくれたほうがいいので、つなげて実行してしまっています

   "export:types": "graphql-codegen && rimraf ../@shared/@types/server && tsc -P tsconfig.shared.json --emitDeclarationOnly",

新規サービスのNestJS + Nuxt3のFull TypeScript環境とセカンドシステム症候群について

2022/8/15にシニアジョブで新規サービスをリリースしました。忘れないうちに振り返りとかをメモ。

まだ想定してる3割ぐらいの機能しかできておらず、これからやること山積みですが、とりあえず動きはするのでWEBサービスのリリースとして許容範囲かなと。

8/30追記: サービス自体については下記社長ブログを参照。 drome5.hatenablog.com

開発から1年を振り返る

新規WEBサービス開発は何度もやってますが、後述の初期要件の大きさもあり、間違いなくコードを人生で一番たくさん書いて学んだ1年間になりました(そのおかげで個人プロジェクトは停滞気味)。

GitHubのプルリクベースで開発しているので改めて振り返ってみると執筆時点の2022/8/25(以下の数字などはこの時点の)でちょうど一年経っている事に気づきました。

件数はマージしなかったのも少しはあるけど1,000超え。

スプレッドシートベースで求人情報とかユーザーとかの項目(≒DBのテーブルとカラム)の要件定義があったのでそこから始めたなぁと思い出しました。

コミット数だと7,887、

私個人に限ると3353コミットでした。

草はきれいに土日祝お休みしてるのがわかります

メンバー

エンジニアは私ともう一人(TypeScriptがっつりは初めて)の計2人、

デザインは最初外注したものの、2022/6にデザイナーさんにジョイン(一瞬で未経験のVue、Nuxt3やTailwindCSSを理解)いただいて1人。

あとSEO担当の方(肩書をなんと呼んでいいか分からない。全知全能)にもコミットしてもらっています。

その他プロマネ的なものは資本的にもプロダクトオーナーでもある社長。

技術選定について

基本的に私が使いたいのを社内メンバーに共有して、いいよってことで使ってる気がします。

いわゆる技術スタックで書くと下記のような感じ。

  • Nuxt3
  • NestJS
  • GraphQL (公開情報のqueryのみ)
  • TypeScript
  • TypeORM
  • PostgreSQL
  • Google Cloud (Cloud Run, Cloud SQL等)
  • Firebase (Google Cloudに含まれそうだけど、でかいので分ける)
  • Elasticsearch (Elastic Cloud)
  • TailwindCSS (PluginとしてTailwind Elementsと管理画面daisyUIを利用)

全部は無理なので大きな下記3つだけ書こうと思います

  • Nuxt3
  • NestJS
  • Firebase

Nuxt3について

私が入社する前から既存のシニアジョブはPHPのLarabel + Vueで、 senior-job.co.jp

自社メディアではNuxt.js(2 + Composition API

を使っていたという前提があり、また私もVueは前前職から仕事で使っていたので フロントエンドにVue系を使用するのは慣れと流れが大きいです。

そして、ちょうど開発当初10月にNuxt3がパブリックベータとなりました。

Nuxt - Introducing Nuxt 3 Beta

正直いつリリースされるかも分からないものを使うのはかなり怖かったんですが、

  • TypeScriptとの相性バツグン(Nuxt.js 2とComposition APIもいろいろストレス)
  • Viteだし早いし開発体験めっちゃいい、auto Importやばっ
  • 開発メンバー優秀すぎる・・・しかもantfuさんまで参加してるし絶対大丈夫
  • まあ、なんかあったら自分でなんとかすればいいや

ってことで勢いでいって、ここまで来てます。

今となっては2を選んでいたらこんな楽しく開発はできてないし、 Nuxt3の機能不足やバグと戦いながらできあがっていくのを眺められて良かったなぁと思ってます。

特にメイン開発者のpi0さんとdanielroeさんの2人強すぎ。

お世話になっているので僅かながらお小遣いの範囲で毎月Nuxtにスポンサーしたりもしました。

問題としてはまだ正式リリースされてないことぐらい。

セキュリティ上の設計として、認証はユーザーとサーバーで直接FirebaseのJWTを使って行うだけにしており、Nuxt3には認証キー的な機密情報は持たせないようにはしています。

その他、Nuxt3に関しては、内部で使っているWEBサーバーNitroやライブラリも、unjsプロジェクトとして開発、公開してたり、2からの移行をサポートするBridgeも同時開発してたり書きたいことはたくさんあるんですがまた今度。

NestJSについて

NestJSはサーバーサイドJavaScriptフレームワークです。もちろんTypeScript対応。 nestjs.com

同じサーバーサイドフレームワークといいつつも、LarabelやRailsDjangoとは全然違い Express(デフォルトのWebサーバー、Fastifyもあり)や TypeORM(ORM、もちろんMongoDB用とか他の選択肢もたくさんもあり)や Appolo(GraphQL)など 既存の有名Nodeライブラリをいい感じにまとめて使いやすくしてくれるフレームワークです。 あとドキュメントがめっちゃいい。

最初の経緯としてはpotato4dさんとお話をしたときに名前は聞いたことがあり、 qiita.com 当然LINEや engineering.linecorp.com メルカリも使ってるし、 メルカリ Shops での NestJS を使った GraphQL Server の実装 | メルカリエンジニアリング 日本の実績もあるんですが、 一番の目的はフロントエンドとサーバーサイドで言語をTypeScript揃えたい!でした。

一度Larabel でAPI、Nuxt.js v2でサイト作ったんですが、どちらも一人でやると脳内の切り替えがつらすぎる・・・。

特にTypeScriptに慣れているのもあり、いろいろ便利(...とか?とか??とかJSONとの相性とか)なのでPHP側がつらすぎでした。

また当然ながらTypeScriptの型を共有できるので、VSCodeの補完やバグの少なさなど開発速度にも影響します。

特にGraphQLがよかった。これについては詳しいのを別で書こうと思っています。

開発人数も大きな理由になります。基本2人しかいないので、サーバー、フロント両方やらざるを得ないので上記メリットが大きくなります。

大きな会社でフロントとサーバーをそれぞれ複数人チームで分けれられる体制ならデメリットにもなりうる(情報の少なさ、採用とか)気がします。

Firebase

認証とストレージ、ホスティングに使用しています。

firebase.google.com firebase.google.com

認証は自社専用管理画面はドメイン制限したGoogle認証、他はメールアドレス、パスワードを使ってます。

ハッシュ化するとはいえ自分でパスワード周りのロジック書きたくないし、保守もしたくない、考えたくもない。 とくにJWTとかどうすりゃいいんだっていう悩みを解決してくれました。

CookieにJWT保存とか絶対やりたくないので、毎回Firebaseのライブラリの処理でリクエストのたびにtokenを取得しています

StorageはまあCloud Storageのただのラッパーなんですが、同じライブラリで使えるので便利です。

ホスティングですが、Nuxt3がFireabse Functionsに早い段階で対応しており、試してみたら全く問題なく動いたのでそのまま管理画面に使っています。 Nuxt 3 - Firebase Hosting

料金もCloud Runより安いはずなのでクソ安いはず。

Functionsだと機能不足や初回起動の遅さとか、謎にエラーが出て動かないときがあったり、いろいろCloud Runに比べると劣ってしまうわけですが、 自社管理画面のような用途であればほぼ困ることはなさそうです。

セカンドシステム症候群との戦い

もう一つの主題はこれ。

今回の新規サービスはシニアジョブにおいて、まさに2つ目の同じようなシステム(要件的にもビジネス的にもいろいろ違うけど長くなるので今回は割愛)です。 senior-job.co.jp

恥ずかしながら名著でもある「人月の神話」をまだ読んだことがなく、ブログなどで断片的に知っているだけでした。

セカンドシステム症候群もたしかTwitterかなんかで見かけて、↓こういう記事で詳しく知って、今の状況まさにこれじゃん・・となりました。 もしかしたら以前も読んだけど課題を抱えてる状態の今だから頭に入ってきただけの説もあり。 人月の神話 | blog.tai2.net

端的に引用するならばこれ

一般的に、二度目のシステムはデザインし過ぎる傾向がある。このとき、最初のシステムデザインでは注意深く外したアイデアと装飾を使う。結果はオビディウスの言ったように(ちりも積もれば*)「山」となる。 第11回:慣れてくると、やりすぎるのが人の常|本気で読み解く”人月の神話”(第5章「セカンドシステム症候群」) - GiXo Ltd.

システム開発に限らず創作とかにもありがちかなぁ事象かなぁと改めて思う。

株式会社シニアジョブは人材紹介、人材派遣を2016年から上記システムとともに歩んできたわけなので、 掲載企業からしたらこういう情報が必要とか、シニアの求職者はこういう情報絶対聞いてくるとかビジネス知見が大量にあるわけです。

で、そんな状態で2つ目を作ろうとしたので、開発前からDBのテーブルやカラムとなる要件定義がスプレッドシートですごいことになり、 当然DBの構築やそれに伴う保存、読み出しコードもすごい量になり、DB周りの準備だけでだいぶ時間を使ってしまいました。

今となっては、要件定義済みのDB全部をつくらず、どこかに絞ってDB ー サーバー ー フロント側までの一連の流れを、まずは一つでも早急に完成させるべきだったと思っています。

が、言い訳として開発初期にはまだNuxt3が公開されてなかった公開直後であったということや、そもそも初めてのNestJSやTypeORMにも不慣れでその習熟期間にもなってたということもあります。

環境に慣れてきた3,4ヶ月後ぐらいには、ある程度の雑さが許される自社用の管理画面で一連の流れを作ることができ、そこからやるべきこともはっきり見えてきて開発スピードが上がっていったような印象があります。

ハードウェアやミドルウェア構成的な面ではなるべく使わないようにして、未だに最低限のDB(PostgreSQL)とElasticsearchのみで、 タスクキュー周りやキャッシュ周り(Redisとか)は使わずになんとか動かしています

まずはどんな小さくてもいろいろ足りなくてもいいから動いて触れるようにする、大事。

Nuxt3 RC4にTailwind Elementsを導入する(RCなので暫定)

RCでまだまだ対応ライブラリの少ないNuxt3ですが、TailwindCSSのモジュール@nuxtjs/tailwindcssは
本体の開発者がメンテナンスしてくれているためか、RC以前から問題なく動いているので使用しています。

今回アニメーション周りの動きなどが欲しくなったのでTailwindプラグインである
Tailwind Elementsをなんとか導入したのでメモ。

前提として前述の@nuxtjs/tailwindcssをインストールしてます
Introduction | Nuxt Tailwind


バージョン

"devDependencies": {
    "@nuxtjs/tailwindcss": "^5.1.3",
    "nuxt": "3.0.0-rc.4",

https://tailwindcss.nuxtjs.org/




Tailwind Elements Quick Start

yarn使ってるのでインストール

yarn add tw-elements

バージョン

 "tw-elements": "^1.0.0-alpha12"

プラグイン周りはドキュメントどおり設定します
tailwindcssの設定はnuxt.config.ts に記述してるのでこんな感じ

import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
// 省略
  tailwindcss: {
    config: {
      content: ['./node_modules/tw-elements/dist/js/**/*.js'],
      plugins: [
        require('tw-elements/dist/plugin')
      ],
//省略

残るJSの読み込みですが、

<script src="./TW-ELEMENTS-PATH/dist/js/index.min.js"></script>

↑のパターンで、useHeadでscriptタグ追加でも動きました。
が、速度的にファイルをまとめてほしいので、
↓こちらのパターンでなんとかやります。

import 'tw-elements';

documentを使用しておりSSRには対応してないので、layoutやpageでimportするとエラーになるため、
client側だけで動くプラグイン利用します。


plugins/tw-elements.client.ts

import * as twElements from 'tw-elements'
export default twElements

内容はimportしてexportするだけ
一行で書けた気もするけどとりあえず動いたのでよし


アコーディオンのサンプルコードをコピペしてアニメーションも動くのを確認できました