Open7

Blitz.jsを触る

YuheiNakasakaYuheiNakasaka

フロントもバックエンドも一貫したい => TypeScript使うのが良さそう => フルスタックフレームワークあるか? => frourioとblitz.jsがある => mizchiさんはfrourioを2020年末には推していた。しかし開発コミュニティの成熟度的にblitz.jsの方に軍配がありそう => よってblitz.jsを使ってみる、という流れ。触ってみる。
https://blitzjs.com/

YuheiNakasakaYuheiNakasaka

ScaffoldはRailsライクなコマンドが使える。interactiveにファイル名を聞いてくるのかわいい。(page, api, query , mutationは予約語なのでmodel名に使えない。)

コマンドのタイプにallを指定でmodel,queries,mutation,pageが生成される。

blitz generate all question text:string

コマンドのタイプにresourceを指定でqueries,mutationのみ生成される。

blitz g resource choice text votes:int:default=0 belongsTo:question

関連をschemaファイルに追加する。

model Question {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  text      String
  choices   Choice[] <= これ
}

model Choice {
  id         Int      @id @default(autoincrement())
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt
  text       String
  votes      Int      @default(0)
  question   Question @relation(fields: [questionId], references: [id])
  questionId Int
}

migrateの実行。

blitz prisma migrate dev

dbをいじるconsleを立ち上げる。なんかprismaのtypeが合わんというエラーが出たが実行自体はできて謎。

blitz console

mutationsに関連のカスケード削除を定義。railsのmodelのhookみたいな感じ。

export default resolver.pipe(resolver.zod(DeleteQuestion), resolver.authorize(), async ({ id }) => {
  await db.choice.deleteMany({ where: { questionId: id } })
  const question = await db.question.deleteMany({ where: { id } })
  return question
})

mutationsに関連のレコード作成を定義。

export default resolver.pipe(resolver.zod(CreateQuestion), resolver.authorize(), async (input) => {
  const question = await db.question.create({
    data: {
      ...input,
      choices: { create: input.choices }, <= ここ
    },
  })
  return question
})

あとは表示、更新とかをmutationいじってpagesいじってを繰り返していくだけだった。

YuheiNakasakaYuheiNakasaka

色々ブラックボックスでわからんことがあったので調べていく。

  • queriesmutationとは?
    • queryは外部リソースへのGET系の操作。mutationは外部リソースへの変更の操作。
  • ReactQueryとは?
    • ReactQueryは外部サーバーとのデータ通信周りやキャッシュ・状態管理などを便利にする機能を提供してくれるライブラリ
  • useMutationとは?
  • refetchとは?
    • useQuery,useMutationで取得したもしくは更新されたデータを再取得してキャッシュを更新する
  • zodとは
    • zodはTypeScriptでスキーマの型定義と検証を行えるようにするライブラリ。GraphQueryみたいなことがpureにできる。
  • resolver.pipeとは
    • resolver.pipeはBlitzのutility関数。関数合成を行える
    • resolver.pipe(func1(input1), func2(func1Output), func3(func2Output));みたいなことができるようになる
    • mutations/queriesのvalidationの適用で利用されてた
  • modelのファイルはどれ?modelディレクトリとかないが?
    • modelはprisma.schemaファイルに記載されるだけ。Railsでいうとこところのmodelが生成されるわけではない。コマンドごとに何が生成されるかはblitz generateのDocを参照
  • OOOはローカルで宣言していますが...というLintのエラーが出てるっぽい。compileは通るけど。
    • exportの無いschema定義をimportしようとしてる。ドキュメントのミスっぽい。Update tutorial.mdx #569でPRが出てるのでいずれ直るはず。
YuheiNakasakaYuheiNakasaka

簡単な個人サイトのようなものを作ってみる

簡単なサイトを作ってみることでフレームワークに馴染めると良さそう。

Chakra UIを使う

yarnで直接入れてもいいがBlitz専用のRecipeというものがあるみたいなのでそれで入れてみる。<Provider/>_app.tsxに勝手に書き換えて設定してくれたりするので便利。

blitz install chakra-ui

Chakra UI自体初めて触るのでまずはChakra UIでのレイアウトの組み方など0からやってみる。FlutterのWidgetみたいな感じのコンポーネントがたくさんあるので一つずつ使ってみる。
https://chakra-ui.com/

zodのバリデーションでエラーが...

mutationでバリデーションエラーが出る。prismaのschema定義にUserとDiaryで関連を書いたがそれが原因か。prismaへの習熟も必要そう...。

forwardRefってなに

https://zenn.dev/terrierscript/scraps/15ca11388f7424

デフォルト引数どう渡せば...

interface DiaryTitleProps {
  date: Date
  fontSize?: string
}

export const DiaryTitle = ({ date, fontSize = "2xl" }: DiaryTitleProps) => {
  return <Text fontSize={fontSize}>{date.toDateString()}</Text>
}

markdown -> html

下記を使った。
https://github.com/markedjs/marked
https://github.com/kkomelin/isomorphic-dompurify

が、syntax highlightが厳しいことが分かったので下記に変更。

https://github.com/remarkjs/react-markdown
https://github.com/react-syntax-highlighter/react-syntax-highlighter

postgresを利用するように変更

公式でも初手でSQLiteから変更するように書いてるっぽいからほんとは早めにやっておくのが良さそう
https://blitzjs.com/docs/tutorial#database-setup

DB用のdocker-compose.yamlを追加。

version: "3.1"

services:
  db:
    image: postgres:13-alpine
    container_name: blitz-blog-postgres
    ports:
      - "5432:5432"
    restart: always
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: password
      POSTGRES_DB: testdb
    volumes:
      - dbdata:/var/lib/postgresql/data
volumes:
  dbdata:

docker-compose up -d

db/schema.prismaを編集。sqliteからpostgresqlに変更。

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

.local.envを編集。

DATABASE_URL=postgresql://admin:password@localhost:5432/testdb

DBのマイグレーション。すでにmigrationディレクトリがあるなら消す。

 blitz prisma migrate dev

GUIからテーブルが作成されていることを確認。

blitz prisma studio

アプリのDocker化

とりあえず何かに使えそうではあるのでDocker化だけはやっておく。

https://github.com/blitz-js/blitz/blob/cefb5c18ce659d29f8c916e16bd8fa512dc4f168/nextjs/docs/deployment.md

  • READMEに書いてあるDockefileでrdocker buildがうまくいかない。Module '"db"' has no exported member 'Prisma'. というエラーが出る。
    • blitz prisma generateが必要だった。Dockerfileの中にRUN ./node_modules/blitz/bin/blitz prisma generateを追加してprismaのclientを生成する必要があった。
  • Build optimization failed: found page without a React Component as default export inエラーが出た。
    • validation.tsファイルをpages配下に置いてたがこれをpagesの外に出した
    • Blitzの公式Tutorialがそうなってたのがよくない
  • DBに接続できない
    • envファイルが読み込まれてなかった
    • docker run --env-file=.env.local -p 3000:3000 docker-nameで起動

デプロイ

GCPのサービスはあんまり馴染みがないのでCloudRun + Cloud SQLで構築してみるか。
https://cloud.google.com/run/docs/quickstarts

独自ドメインもサクッと当てられるみたいなので良さそう(選択するリージョンには注意)。
https://zenn.dev/mseto/articles/cloud-run-domain

誤算

ここでDBを作成しようと価格を調べたら最低レベルのCloud SQLで$12/monthなので高いことが判明...。ちょっと別の方法考える。

Blitz公式ではVercel + PlanetScaleRenderRailsWay、その他Herokuを紹介してる。

RenderはDBを使うのに$7/monthかかる。RailsWayはDBを使用するアプリのシミュレーションで$1.2/month。PlanetScaleは10GBのStorageで100million read,10million writeまで無料。ちょっと面倒かなと思ってたけどDB hostingとしてPlanetScaleを使うのが一番安そう。

Vercel + PlanetScale

PlanetScale公式のDeploy to Vercelを参考にする。Blitzの場合はPrismaのAuto Migration設定もしておいたほうが良さそう。

  1. CLIのinstall
brew install planetscale/tap/pscale
  1. ログイン
pscale auth login
  1. dbの作成
pscale database create --region ap-northeast YOUR_DB_NAME
pscale branch create YOUR_DB_NAME development
  1. テーブルの作成
    ここがよくわからなかったんだけど、db/migrations/xxxxxxxxx/migration.sqlの中のテーブルをshellで追加した。もっといい方法がないのかな。
pscale shell personal-site development

とぽちぽち進めていったら外部キー制約が使えないと判明。クソ....betaだから仕方ないか。
https://vitess.io/blog/2021-06-15-online-ddl-why-no-fk/

  1. dbのmainブランチをProductionに設定する
    webのGUIからPromote Branchみたいなやつがあるのでそれでmainを設定する。

  2. production dbのパスワードを作成
    https://docs.planetscale.com/tutorials/connect-any-application#connect-to-your-database

  3. Vercelにデプロイする準備
    VercelでNew Projectから。

  4. 5で作成したパスワード等を使ってDATABASE_URLを環境変数に設定
    こんな感じ↓

DATABASE_URL=mysql://USER_NAME:PASSWORD@HOST_NAME:3306/DB_NAME?sslmode=require&sslcert=/etc/pki/tls/certs/ca-bundle.crt
  1. SESSION_SECRET_KEYを環境変数に追加
    セッションの署名用のkeyを追加する。下記で生成できる。
openssl rand -hex 16
  1. デプロイ

カスタムドメインを当てる

vercel側のdomainsからカスタムドメインを設定する。AレコードのIPを取得して、お名前.comにAレコードに設定。10分くらいでVercel側の確認がvalidになる。

できた↓
https://razokulover.com/

YuheiNakasakaYuheiNakasaka

個人サイトの日記を書くためのエディタ用に使ってたreact-markdownを7系にアップロードしたらそれを使用してるpages/配下のファイルのファイル生成においてVercelのデプロイが失敗するようになった。

エラーメッセージはfound page without a React Component as default export という感じ。公式の文章を見るとどうもPageのReact Componentが正しくexport default出来てないよということらしい。
https://nextjs.org/docs/messages/page-without-valid-component#why-this-error-occurred

しかしながら該当するファイルを確認してもBlitzPageは正しく実装出来ているように見える。

とここでreact-markdownが怪しいと思い、当該ライブラリのChangeLogを読んだところ7系からESMのみの実装に切り替わったらしい。

ESMについて詳しくないので調べた。CJSとESMについては下記が完結にまとまってた。
https://numb86-tech.hatenablog.com/entry/2020/08/07/091142

Next.jsはv11.1からexperimentalであるところのesmExternals: trueを設定するとESMのパッケージに対応できるらしい。が、今使ってるBlitz.jsのNext.jsはv11.1だがesmExternals: trueになってない(ように見える)。
https://nextjs.org/blog/next-11-1#es-modules-support

ということで、つまり現状のBlitz.jsではESMのみ対応のreact-markdownのv7系はVercelでは使えない。

この理解で合ってるか...?

確認のためreact-markdownのバージョンをv6.0.3に落としてみた。すると動いた。