EY-Office ブログ

ZenStackはドキュメントは最悪だが良いライブラリーかもしれない

先々週の Next.jsのReact Server Componentsを試してみた を書くために色々と調べていたときに ZenStack というライブラリーを見つけました。このブログが初の日本語の記事かもしれません。😃

ZenStackを使うとReact Server Componentsや Rimix(本ブログ) のようにReactフロントエンド+バックエンドを簡単に書けるライブラリーです。

実は先週このZenStackの記事に書こうと、先々週末に Get Started With Next.js を見ながら、いつものジャンケンアプリを試してみたのですが不可思議な問題にハマり抜け出せませんでした。
先週末に再度トライしたところ問題点が解決でき いつものジャンケンアプリ が動いたので、今週のブログが書けました。

ZenStack

ZenStackとは

ZenStackホームページのZenStack には以下のように書かれています(DeepL翻訳です)。

  • 🛠️ カスタム属性をサポートする拡張スキーマ言語
  • 🔐アクセスポリシーとデータ検証ルール
  • 🚀 自動CRUD API - RESTful、tRPC
  • 🤖 クライアントサイドのデータアクセスライブラリ(別名フック)の生成 - SWR、TanStack Query
  • 🧩 優れた拡張性のためのプラグインシステム

私の理解では、

  • 今やNode.jsのデファクトスタンダードORM Prismaを拡張するライブラリー
    • ユニーク属性@uniqueなどのカスタム属性のサポート
    • 宣言的Validationのサポート
    • モデル(table)単位での宣言的アクセス権限のサポート
      • 例: ログイン済みユーザーのみ書き込み可
  • Next.js等を使った Prisma Client API ベースのAPIサーバー機能
    • GET http://localhost:3000/api/model/score/findManyでscoreモデルのfindMany関数が実行されます
  • フロントエンド(たとえばReact + SWR)用データベースアクセス・ライブラリーのコード生成
  • ZenStack自体はプラグイン・アーキテクチャなので色々な機能やライブラリーが組み込める作りになっている

いつものジャンケンアプリをZenStackで動かしてみた

まずは、結果を見てもらいましょう。いつものジャンケンアプリ のメインのコンポーネントは以下のようになります。

  • JyankenBoxやScoreBoxは いつものジャンケンアプリ と同じです。
  • useFindManyScore、useMutateScoreはZenStackが生成したデータベースアクセス・ライブラリーのHooksです
import JyankenBox, { Te } from "@/components/JyankenBox";
import ScoreBox, { Jjudgment } from "@/components/ScoreBox";
import { useFindManyScore, useMutateScore } from "@/lib/hooks";

export default function Home() {
  const { createScore } = useMutateScore();                               // ← ①
  const { data, isLoading } = useFindManyScore({orderBy: {id: 'desc'}});  // ← ②

  const pon = async (human: Te) => {
    const computer: Te = Math.floor(Math.random() * 3);
    const judgment: Jjudgment = (computer - human + 3) % 3;
    await createScore({data: {human, computer, judgment}});              // ← ③
  }

  return (
    <>
      <h1>じゃんけん ポン!</h1>
      <JyankenBox actionPon={pon} />                                {/* ← ④ */}
      {isLoading && <p>Loading...</p>}                              {/* ← ⑤ */}
      {data && <ScoreBox scores={data} />}                          {/* ← ⑥ */}
    </>
  )
 }
  • ① 更新系の関数はuseMutateScore Hookから得られます
  • ② 取得系の関数は関数単位でHookになっています
    • Prismaのprisma.score.findMany相当の関数で、データベースからデータを取得します
    • SWRがベースになっているので戻り値にはデータ(data)、エラー情報(error)以外に、データ取得中(isLoading)などがあります
  • ③ Prismaのprisma.score.create相当の関数で、ジャンケン結果をデータベースに挿入します
  • ④ ジャンケン・ボタンの並んだJyankenBoxコンポーネント
  • ⑤ データ取得中(isLoading)の表示
  • ⑥ データが取得された場合、ScoreBoxコンポーネントでデータを表示

このように、アプリのコードにはバックエンドとの通信やバックエンド(APIサーバー)のコードはなく、Prisma Client APIから簡単に想像できるデータベースアクセス関数が用意されていて、簡単にバックエンドを含むReactフロントエンドが作成できます。
しかも Next.jsのReact Server ComponentsRimix よりも簡単そうにみえますね。

ZenStack入門

さて、ZenStackホームページの Get Started With Next.js の手順通りに作っていけばサンプルアプリが出来そうですが、実際に作ってみるとimport先ファイルが無かったりして上手くいきませんでしたので、今回のジャンケンアプリを作る手順を書いておきます。

1. 準備

Next.jsのプロジェクトを作り、zenstack用のプロジェクトに設定します。

$ npx create-next-app@latest zen_jyanken
✔ Would you like to use TypeScript? …  Yes
✔ Would you like to use ESLint? …  Yes
✔ Would you like to use Tailwind CSS? … No
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … No
✔ Would you like to customize the default import alias? … No

$ cd zen_jyanken
$ npx zenstack@latest init
2. モデル(table)定義

./schema.zmodel ファイルにPrismaのモデルを定義します。
また、ZenStack独自のVaidationやアクセス権限も定義できます。 詳細→ ZModel Language Reference

  • ./schema.zmodel
datasource db {
  provider = 'sqlite'
  url = 'file:./dev.db'
}

generator client {
  provider = "prisma-client-js"
}

plugin hooks {
  provider = '@zenstackhq/swr'   // ← ①
  output = "./src/lib/hooks"
}

model Score {
  id         Int @id @default(autoincrement())
  human      Int
  computer   Int
  judgment   Int
  created_at DateTime @default(now())

  @@allow('all', true)           // ←  ②
}
  • ① 通信にSWRを使うためにプラグイン定義
  • ② アクセス権限の定義
    • これは、全操作が全ユーザーで行えます
    • 今回のサンプルでは無くても動きました
3. データベース作成

Prismaのschema作成とデータベース(sqlite3)作成

$ npx zenstack generate
$ npx prisma db push
4. SWRのインストール
$ npm install @zenstackhq/server swr
$ npm install --save-dev @zenstackhq/swr
5. APIサーバーの作成

./src/pages/api/model/[...path].tsファイルを作り以下のように書き、フロントエンドからのリクエストを受け付ける関数を作ります。
Get Started With Next.jsでは認証機能等を加えていますが、ここでは認証なしのシンプルなAPIサーバーにします。

  • ./src/pages/api/model/[...path].ts
mport { NextRequestHandler } from '@zenstackhq/server/next';
import { enhance } from '@zenstackhq/runtime';
import type { NextApiRequest, NextApiResponse } from 'next';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

async function getPrisma(_req: NextApiRequest, _res: NextApiResponse) {
  return enhance(prisma, {});
}

export default NextRequestHandler({ getPrisma });
6. フロントエンド用データベースアクセスライブラリーの作成
$ npx zenstack generate
7. フロントエンドの作成
8. 実行
$ npm run dev

まとめ

ZenStack

  • デファクトスタンダードORM Prismaの拡張ライブラリー
  • シンプルなAPIサーバー
  • フロントエンド用データベースアクセス・ライブラリーの自動生成

というシンプルなライブラリーの集まりのようなものです。

React Server Components(RSC)やRemixのような、色々なルールがあるフレームワークと違い、Reactベースのフロントエンド+バックエンドが簡単に作れる良いライブラリーだと思いました。

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ