📘

Blitz.jsのチュートリアルを全部読んで、まとめてみた

2023/05/13に公開

本記事について

この記事では、Blitz.jsを触ったことのない著者が公式ドキュメントを読んで、1から学んだものになります。
随時更新中...

参考文献

https://blitzjs.com/

Ship. Fast.

Blitz.jsはNext.jsアプリケーションのフルスタックツールキットです。

  • 型安全なAPIレイヤー
    • Blitz RPC を使用すると、REST、GraphQL、またはバニラ API ルートよりも 10 倍高速に機能を構築して反復できます。HTTP やシリアル化で混乱することなく、完全なタイプセーフでクライアントからデータを読み書きします。
  • 認証
    • Next.js の強力で柔軟な、実戦テスト済みの認証と認可が実現可能です。他の API よりもシンプルな API で、より詳細な制御が可能になります。Google、Github、Auth0 などのソーシャル サードパーティ統合を簡単に追加できます。
  • 規約とコード生成
    • 新しいアプリを 2 週間ではなく 2 分でセットアップします。認証、ユーザー サインアップ、パスワードを忘れた場合の処理、選択したフォーム ライブラリなど、すべてが自動的に設定されます。

ビデオを見てみよう

ブランドン・バイヤーはRenderATL 2022でのプレゼンテーションで、Blitz.jsというNext.jsアプリケーションのツールキットを紹介しました。陽気な方なのでめっちゃ面白いです。
https://www.youtube.com/watch?v=8D8yox2clug

時間がない人向けのサマリー

  • ブランドン・バイヤーはRenderATL 2022でのプレゼンテーションで、Blitz.jsというNext.jsアプリケーションのツールキットを紹介しました。
  • 彼は子供の頃に四輪車をハックした経験や、フリーランスとしてNext.jsアプリの複雑さに悩んだ経験を共有しました。
  • ブランドンはRuby on Railsを使った経験もあり、誰かがReact用のRailsを作ってくれることを願っていましたが、そのような人が現れなかったため自身でBlitz.jsを作ることを決め、開発者の経験においてシンプルさ、コントロール性、抽象化の最小化を重視しました。
  • Blitz.jsには新しいプロジェクトのためのコードスキャフォールディング、革新的な認証システム(Blitz Auth)、Blitz RPCと呼ばれる新しいAPI層が含まれています。
  • Reactのデータ取得に関して、No REST, NO GraphQL, NO HTTP. Only need function and dataという仕組みをタイのとある島でハンモックに横たわりながら思いついた。
  • 上記のアプローチの3つの強み。1. 別個のコード生成プロセスを使用することなく、E2Eの型安全性を保証する2. 日付など複雑な方を自動的にシリアル化および逆シリアル化するので、バックエンドとフロントエンド間で日付をストリームに変換する必要はない,フルスタックエラー処理が可能になる。サーバー側でスローされたでエラーを、クライアント側でキャッチできる
  • 最新バージョンであるBlitz 2.0では、Blitz.jsがモジュラーなツールキットに進化し、任意のNext.jsアプリケーションで使用できるようになりました。モバイルアプリのサポートやバックグラウンド処理、ファイルのアップロードなどの機能が追加されています。また、デプロイメントのためのプロジェクトであるflightcontrol(https://www.flightcontrol.dev/)にも言及しました。

なぜ、Blitz.jsを使用するのか

  1. Blitz.jsはNext.jsでフルスタック開発ができるようにするため、不足している機能をサポートする。(タイプセーフ API レイヤー、ミドルウェア、認証など)

  2. Blitz.jsを使用すると、サーバーサイドのコードをフロントエンドに直接インポートできるため、従来のようにサーバーサイドでAPIを構築してフロントエンドからデータをフェッチする必要がない。ビルド時に、Blitz.jsはサーバー上でサーバーサイドのコードを実行する RPC API呼び出しを自動的に挿入します。

  3. Blitz は、セルフホスト型電子メール/パスワードやサードパーティサービスなど、あらゆる ID プロバイダーと連携するセッション管理を提供する。認証周りの実装が必要ない。

  4. 手動で記述しなければならないコードの量を減らすために、コードのスキャフォールディングを提供する。

    • Blitzのスキャフォールディングは、開発者が新しいプロジェクトや新しい機能を迅速に開始できるようにするためのコード生成ツールを提供し、例えばBlitzのscaffoldコマンドは、CRUD(Create, Read, Update, Delete)操作を完全にサポートする一連のファイルを生成する。これには、データベースモデル、APIエンドポイント、およびフロントエンドのコンポーネントが含まれており、blitz generate all projectというコマンドを実行すると、新しいプロジェクトのモデル、ページ、ミューテーション、クエリ、その他の必要なファイルが自動で生成される。
  5. 新しいBlitz.jsアプリでは、eslint、prettier、husky git hooksなどが事前に設定されているため、環境構築の時間を大幅に節約できます。後からいつでもカスタマイズ可能。

  6. Blitz.jsには、ルートマニフェスト(https://blitzjs.com/docs/route-manifest)があるため、以下のようにページのリンクを定義できる。

TOBE:
<Link href={Routes.ProductsPage({ productId: 123 })} />

ASIS:
<Link href={`/products/${123}`} />

さあ、Blitz.jsを始めよう

必要な環境

name version
node.js 16以上

Blitz.jsのインストール

yarn global add blitz
# or
npm install -g blitz

新しいアプリの作成

blitz new myAppName
cd myAppName
blitz dev
# http://localhost:3000

学習パス

Blitz.jsを1から学ぶ人向けの参考文献がまとめられています。1つずつ紹介するのは省略します。
https://blitzjs.com/docs/learning-path

チュートリアル

Blitz.jsがインストールされているか確認

blitz -v

新しいアプリの作成

blitz new my-blitz-app

作成されたディレクトリの構造

my-blitz-app
├── src/
│   ├── auth/
│   │   ├── components/
│   │   │   ├── LoginForm.tsx
│   │   │   └── SignupForm.tsx
│   │   ├── mutations/
│   │   │   ├── changePassword.ts
│   │   │   ├── forgotPassword.test.ts
│   │   │   ├── forgotPassword.ts
│   │   │   ├── login.ts
│   │   │   ├── logout.ts
│   │   │   ├── resetPassword.test.ts
│   │   │   ├── resetPassword.ts
│   │   │   └── signup.ts
│   │   └── validations.ts
│   ├── core/
│   │   ├── components/
│   │   │   ├── Form.tsx
│   │   │   └── LabeledTextField.tsx
│   │   └── layouts/
│   │       └── Layout.tsx
│   ├── users/
│   │   ├── hooks/
│   │   │   └── useCurrentUser.ts
│   │   └── queries/
│   │       └── getCurrentUser.ts
│   ├── pages/
│   │   ├── api/
│   │   │   └── rpc/
│   │   │       └── [[...blitz]].ts
│   │   ├── auth/
│   │   │   ├── forgot-password.tsx
│   │   │   ├── login.tsx
│   │   │   └── signup.tsx
│   │   ├── _app.tsx
│   │   ├── _document.tsx
│   │   ├── 404.tsx
│   │   └── index.tsx
│   ├── blitz-client.ts
│   └── blitz-server.ts
├── db/
│   ├── migrations/
│   ├── index.ts
│   ├── schema.prisma
│   └── seeds.ts
├── integrations/
├── mailers/
│   └── forgotPasswordMailer.ts
├── public/
│   ├── favicon.ico*
│   └── logo.png
├── test/
│   └── setup.ts
├── README.md
├── next.config.js
├── vitest.config.ts
├── package.json
├── tsconfig.json
├── types.d.ts
├── types.ts
└── yarn.lock
  • src/ : blitz.jsのセットアップをするblitz-client.tsとblitz-server.tsが置かれており、queries/, mutations/, components/が配置される
    • おすすめの構造は、core/,users/,auth/,answers/のようにドメインコンテキストとなる名前のフォルダを第1層に名付ける。その下にcomponents/,hooks/,queries/,mutations/を共通して配置する。
    • queries/およびmutations/はBlitz.jsクエリとミューテーション用であり、そのファイルパスに対応するURLで公開されます。
  • src/pages/ : 全てのページとAPIルートをここに配置する
    • ここにあるすべてのファイルは、ファイル パスに対応する URL にマップされる
  • src/pages/api : フォルダ内のファイルは全て/api/*にマップされ、ページではなくAPIエンドポイントとして扱われます。
  • src/core/ : アプリ全体で使用されるコンポーネントやフックなどを配置する
  • db/ : データベース構成、スキーマ、移行ファイルが含まれる。モデルを生成したり、移行を確認したりする場合はここを参照
    • db/index.js初期化されたデータベース クライアントをエクスポートすると、アプリ内のどこでも使用できるようになる。
  • integretions/ : サードパーティの統合コードが含まれている。
    • 例: auth0.js、twilio.jsなど、共有設定を使用してクライアントをインスタンス化するのに適している。
  • public/ : 静的アセットを配置する。
  • .npmrc, .env : 構成ファイル
  • next.config.js : Blitz.jsとNext.jsのカスタム設定
  • tsconfig.json : TypeScriptの推奨設定

その他

すべての最上位フォルダーには自動的にエイリアスが付けられるため、たとえば、src/projects/queries/getProjectはアプリ内のどこからでもインポートできる

参考文献

https://blitzjs.com/docs/file-structure

ローカルで実行する

blitz dev

すると、コマンドラインに次のように出力される

✔ Compiled
Loaded env from /private/tmp/my-blitz-app/.env
warn  - You have enabled experimental feature(s).
warn  - Experimental features are not covered by semver, and may cause unexpected or broken application behavior. Use them at your own risk.

ready - started server on 0.0.0.0:3000, url: http://localhost:3000
event - compiled successfully

ユーザー登録

Blitzアプリは、ユーザーのサインアップとログインが既に設定された状態で作成されるので、試しにサインアップボタンをクリックする。任意のメアドとパスワードでアカウントの作成をクリックすると、ホームページにリダイレクトされてuseridroleが表示される。

データベースのセットアップ

blitz prisma studio

こちらを実行すると、Webインタフェースが開き、データベースないのデータを確認できる。デフォルトではSQLiteデータベースが設定されているが、よりスケーラブルなデータベースを使用するためPostgreSQLを使用することをお勧めする。

参考文献

https://blitzjs.com/docs/database-overview

データベースの概要

デフォルトでは、Blitz.jsは厳密に型定義されたデータベースクライアントであるPrismaを使用しているため、ドキュメントを読む。(https://www.prisma.io/docs/concepts/overview/what-is-prisma)

1分で見れるビデオ

https://www.youtube.com/watch?v=EEDGwLB55bI

忙しい人のためのサマリー

  • Prismaはオープンソースの次世代ORMで、Node.jsとTypeScript向けの自動生成型安全クエリビルダーPrisma Client、データベースマイグレーションシステムPrisma Migrate、データベースのデータを視覚的に閲覧・編集するGUIPrisma Studioの三つの部分で構成されている。
  • Prismaスキーマファイルを通じてデータモデルを定義し、データベースに接続する。データモデルは手動で作成したり、データベースを内部調査して生成したりする。
  • Prisma Clientはnpmでインストールし、データベースにクエリを送信します。また、Prisma MigrateやSQLマイグレーションと内部調査を使用した異なるワークフローを提供します。

ORMを初めて聞いたよって人向けに解説

ORM(Object-Relational Mapping)はオブジェクト指向プログラミングとリレーショナルデータベースの間に存在するパラダイムの不一致を解決するための技術。

基本的には、データベースのテーブルをプログラムのオブジェクトとして表現し、SQLクエリの代わりにオブジェクト指向の方法を使用してデータベースと対話する。このアプローチにより、開発者はデータベース固有のクエリ言語(SQLなど)の知識が少なくても、アプリケーションとデータベースの間のデータの流れをより簡単に制御できる。

// 例
model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  name    String?
  role    Role     @default(USER)
  posts   Post[]
  profile Profile?
}

ORMの利点:

  • 生のSQLクエリを書く必要がなく、開発者はビジネスロジックに集中できる。
  • データベーススキーマの変更が容易になる。ORMはスキーマの変更を自動的にハンドルし、データベースとアプリケーションの間の不一致を最小限に抑える。
  • コードの可読性と保守性が向上する。データベース操作はオブジェクト指向のメソッドとして表現され、より直感的に理解できる。
  • ORMは通常、クエリの最適化、キャッシング、トランザクション管理など、多くの高度な機能を提供する。

ORMの欠点:

  • 複雑なクエリや特殊なデータベース操作を行う場合、ORMは限界を持つ場合がある。これは、ORMが一般的なデータベース操作を抽象化することに焦点を当てているため。
  • ORMはある程度の学習曲線を伴う。特に、開発者がオブジェクト指向プログラミングとリレーショナルデータベースの両方に習熟していない場合、理解と使用が難しくなる可能性がある。
  • パフォーマンス問題が発生する可能性がある。ORMは抽象化のレベルを高めるため、手書きのSQLクエリよりもパフォーマンスが低下することがある。ただし、多くの場合、このパフォーマンスの違いはほとんどまたは全く気付かれない。

データベーステーブルの追加

db/schma.prismaを開いて、次のようなモデルを追加する

model Project {
  id        Int      @default(autoincrement()) @id
  name      String
  tasks     Task[]
}

model Task {
  id          Int      @default(autoincrement()) @id
  name        String
  project     Project  @relation(fields: [projectId], references: [id])
  projectId   Int
}

次にデータベースの変更を適用する

blitz prisma migrate dev

これで、dbからdb/index.tsにモデルをimportできるようになる。

// 例1
db.project.create({data: {name: 'Hello'}})
// 例2
db.task.findFirst({ where: { id: unsafeId } });

データベース操作

Blitz.jsはPrisma Clientを使用してデータベース操作を行う。したがって、Blitz.jsのデータベース操作はPrisma ClientのAPIに基づいている。以下にPrisma Clientの主な操作をいくつか紹介する:

  • create:新しいレコードを作成。
  • findUnique:一意のレコードを見つる。一般的には、一意のIDや他の一意のフィールドを使用する。
  • findMany:条件に合うすべてのレコードを見つける。
  • findFirst:条件に一致する最初のレコードを見つける。複数のレコードが条件に一致する場合でも、最初の1つだけが返される。
    • findFirstOrThrow:条件に一致する最初のレコードを見つける。複数のレコードが条件に一致する場合でも、最初の1つだけが返される。クエリが見つからない場合にはNotFoundError: No User found errorを返す
  • update:既存のレコードを更新する。
  • updateMany:一致するすべてのレコードを更新する。
  • delete:既存のレコードを削除する。
  • deleteMany:一致するすべてのレコードを削除する。
  • upsert:レコードが存在する場合は更新し、存在しない場合は新規作成する。
  • count:一致するレコードの数を返す。
  • aggregate:一致するレコードの集約を取得する。例えば、最大値、最小値、平均値、合計などを取得することができる。

また、Prisma Clientにはいくつかの特別なメソッドがあり、これらは他の通常のデータベース操作メソッドとは異なり、$をプレフィックスとして持っている。これらのメソッドは一般的に高度なユースケースや低レベルのデータベース操作のために提供されている:

  • db.$connect():データベースへの接続を明示的に開く。通常、Prisma Clientは初めてクエリを実行する際に自動的に接続を開くため、このメソッドを明示的に呼び出す必要はありません。
  • db.$disconnect():データベースから明示的に切断する。Node.jsのスクリプトが終了したときやサーバーレス関数が終了したときには、このメソッドを呼び出してリソースを解放することが推奨されている。
  • db.$transaction():複数のクエリを一つのトランザクションとして実行する。これにより、全てのクエリが成功するか、一つでも失敗した場合は全てロールバックされるというACID特性を確保することができる。
  • db.$queryRaw():SQLクエリを直接実行する。Prisma Clientが提供する型安全なクエリビルダーでは表現できない複雑なクエリや、特定のデータベースエンジンに固有の機能を利用するために使用する。
  • db.$executeRaw():SQLクエリを直接実行し、影響を受けた行数を返す。これは主にUPDATEやDELETEのような非選択クエリに使用する。
  • db.$queryRawUnsafe():db.queryRawと同様に、データベースへの生のSQLクエリを実行するために使用するが、このメソッドでは値のパラメータ化が行われない。そのため、SQLインジェクション攻撃に対して脆弱になる。queryRawUnsafeは、他の方法でクエリを安全にすることができない限り、使用するべきではあない。
// 例
const result = await db.$transaction([
  db.user.create({ data: { name: 'Alice' } }),
  db.post.create({ data: { title: 'Hello World' } })
]);

const users = await db.$queryRaw`SELECT * FROM users WHERE email = ${email}`;

const users = await db.$queryRawUnsafe('SELECT * FROM users');

これらのメソッドは、通常のPrisma Client APIではカバーできない特殊なユースケースに対応するために提供されている。しかし、これらは低レベルの操作を提供するため、使用には注意が必要。例えば、$queryRaw()$executeRaw()はSQLインジェクション攻撃に対して脆弱な可能性があるため、パラメータを適切にエスケープするなど、セキュリティ上の懸念に対する適切な対策が必要。

これらの操作はすべて、データベースのテーブルやコレクションに対して行われる。また、これらの操作はPromiseを返し、そのため非同期的に実行される。

参考文献

https://www.prisma.io/docs/concepts/components/prisma-schema/data-model
https://www.prisma.io/docs/reference/api-reference/prisma-client-reference

PostgreSQLへの切り替え

  1. Postgresをローカルにインストールして実行する(https://blitzjs.com/docs/postgres)
  2. db/schema.prismaを変更する
datasource db {
  provider = "postgres"
  url      = env("DATABASE_URL")
}
  1. .env.localのDATABASE_URLを追加変更する。
  2. db/migrations/フォルダを削除する
  3. 以下のコマンドを実行し、スキーマに基づいてデータベースとテーブルを作成する
blitz prisma migrate dev

モデルをスキャフォールディングする

Blitz は、ボイラープレート コードをスキャフォールディングするための便利なCLIコマンドgenerateを提供している。まずはQuestionモデルに関連するものを全て生成する。

blitz generate all question text:string

Enterを押すと、prisma migrateが実行され、データベーススキーマが新しいモデルで更新される。

CREATE    src/pages/questions/[questionId].tsx
CREATE    src/pages/questions/[questionId]/edit.tsx
CREATE    src/pages/questions/index.tsx
CREATE    src/pages/questions/new.tsx
CREATE    src/questions/components/QuestionForm.tsx
CREATE    src/questions/queries/getQuestion.ts
CREATE    src/questions/queries/getQuestions.ts
CREATE    src/questions/mutations/createQuestion.ts
CREATE    src/questions/mutations/deleteQuestion.ts
CREATE    src/questions/mutations/updateQuestion.ts

✔ Model 'Question' created in schema.prisma:

>
> model Question {
>   id        Int      @id @default(autoincrement())
>   createdAt DateTime @default(now())
>   updatedAt DateTime @updatedAt
>   text      String
> }
>

✔ Run 'prisma migrate dev' to update your database? (Y/n) · true
Environment variables loaded from .env
Prisma schema loaded from db/schema.prisma
Datasource "db": SQLite database "db.sqlite" at "file:./db.sqlite"

✔ Enter a name for the new migration: … add question
The following migration(s) have been created and applied from new schema changes:

migrations/
  └─ 20210722070215_add_question/
    └─ migration.sql

Your database is now in sync with your schema.

✔ Generated Prisma Client (4.0.0) to ./node_modules/@prisma/client in 187ms

blitz generate all question text:string コマンドはBlitz.jsフレームワークを使用して、新たなCRUD(Create, Read, Update, Delete)オペレーションをサポートするためのコードを生成する。

具体的には以下のような動作をする:

  • questionという名前のモデルを持つ新たなPrismaスキーマを生成する。このモデルは text という名前の string 型のフィールドを持つ。
  • Prisma Client用のクエリとミューテーションを含む、questionモデルに対するAPIエンドポイントを生成する。これには、データの作成、読み込み、更新、削除(CRUD)が含まれる。
  • questionモデルに対する新たなページを生成する。これには一覧ページ(リスト)と詳細ページ(詳細表示と編集)が含まれる。
  • questionモデルに対する新たなReactコンポーネントを生成する。これには一覧表示と詳細表示用のコンポーネントが含まれる。
  • 必要な場合は、データベースマイグレーションファイルを生成する。これにより、データベーススキーマが新たなquestionモデルに合わせて更新される。
  • これにより、データベース、APIエンドポイント、フロントエンドページとコンポーネントが一貫した状態で保たれ、新たなquestionエンティティをアプリケーションで使用できるようになる。生成されたコードは開発者がカスタマイズ可能で、特定のビジネスロジックやUIの要件に合わせて変更できる。

generate コマンドについて

page, queries, mutations, prismaモデルを生成できる。

blitz generate [type] [model] [fields]

出力例

blitz generate all project

app/pages/projects/[projectId]/edit.tsx
app/pages/projects/[projectId].tsx
app/pages/projects/index.tsx
app/pages/projects/new.tsx
app/projects/components/ProjectForm.tsx
app/projects/queries/getProject.ts
app/projects/queries/getProjects.ts
app/projects/mutations/createProject.ts
app/projects/mutations/deleteProject.ts
app/projects/mutations/updateProject.ts

対応表

引数 必須か 説明
type Yes 生成するファイルのタイプ
model Yes ファイルを生成するモデル名
fields Yes 生成するモデルのフィールドのリスト
Type Model Queries Mutations Pages
all Yes Yes Yes Yes
resource Yes Yes Yes
model Yes
crud Yes Yes
queries Yes
query Yes
mutations Yes
mutation Yes
pages Yes

オプション

  • context/model
    プロジェクト内のファイルを整理するために、ファイルを生成するネストされたフォルダーパスを指定可能。
blitz generate all admin/products
# Will generate files in `app/admin/products` instead of `app/products`
  • --parent, -p
    親モデルの子であるモデルのファイルを生成することを指定するために使用される
  • --dry-run,-d
    どのようなファイルが生成されるかを表示するが、ファイルはディスクに書き込まれない
  • --env,-e
    アプリ環境名を設定できる(https://blitzjs.com/docs/custom-environments#custom-environments)

参考文献

https://blitzjs.com/docs/cli-generate

Discussion