先々週の Next.jsのReact Server Componentsを試してみた を書くために色々と調べていたときに ZenStack というライブラリーを見つけました。このブログが初の日本語の記事かもしれません。😃
ZenStackを使うとReact Server Componentsや Rimix(本ブログ) のようにReactフロントエンド+バックエンドを簡単に書けるライブラリーです。
実は先週このZenStackの記事に書こうと、先々週末に Get Started With Next.js を見ながら、いつものジャンケンアプリを試してみたのですが不可思議な問題にハマり抜け出せませんでした。
先週末に再度トライしたところ問題点が解決でき いつものジャンケンアプリ が動いたので、今週のブログが書けました。
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の
prisma.score.create
相当の関数で、ジャンケン結果をデータベースに挿入します - ④ ジャンケン・ボタンの並んだJyankenBoxコンポーネント
- ⑤ データ取得中(isLoading)の表示
- ⑥ データが取得された場合、ScoreBoxコンポーネントでデータを表示
このように、アプリのコードにはバックエンドとの通信やバックエンド(APIサーバー)のコードはなく、Prisma Client APIから簡単に想像できるデータベースアクセス関数が用意されていて、簡単にバックエンドを含むReactフロントエンドが作成できます。
しかも Next.jsのReact Server Components 、Rimix よりも簡単そうにみえますね。
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. フロントエンドの作成
./src/pages/index.tsx
ジャンケン・メイン 最初のコード./src/components/JyankenBox.tsx
手の並んだコンポーネント いつものジャンケンアプリと同じ./src/components/ScoreBox.tsx
結果表示コンポーネント いつものジャンケンアプリと同じ./src/pages/_app.tsx
globals.cssの影響を消すために、import '@/styles/globals.css'
を削除
8. 実行
$ npm run dev
まとめ
ZenStack は
- デファクトスタンダードORM Prismaの拡張ライブラリー
- シンプルなAPIサーバー
- フロントエンド用データベースアクセス・ライブラリーの自動生成
というシンプルなライブラリーの集まりのようなものです。
React Server Components(RSC)やRemixのような、色々なルールがあるフレームワークと違い、Reactベースのフロントエンド+バックエンドが簡単に作れる良いライブラリーだと思いました。