Convexという興味深いバックエンド・サービス/開発プラットフォームを知ったので試してみました。
Convexは、リアルタイムDBを中心にしたバックエンド・サービスです、Firebaseのようなサービスと言えば伝わりやすいでしょうか。ただし、
- JavaScriptレベルのAPI提供ではなく、Reactから使いやすいHooksをサポートしています
- バックエンドソフトもOSSで自前のサーバーでも運用できるようです
Convexホームページから
リアルタイムDB
実はリアルタイムDBを扱うのははじめてで、とても新鮮です! 下の画像(アニメーションGIF)はいつものジャンケンアプリをです、2つのウィンドウで同じアプリを動かしています。右のウインドウのアプリのボタンを押すと左のアプリの結果も同時に更新されています。
コード説明
Convexを使って驚いたのは、Reactとの親和性の高さです!
src/App.tsx
このコードの基になった純粋なReactのコードではジャンケンの結果はStateで持っていました → Ⓐ 。
export default function App() {
const [scores, setScores] = useState<ScoreType[]>([]); // ← Ⓐ
const pon = (human: Te) :void => {
const computer = randomHand();
const judgment = judge(computer, human);
setScores([{human, computer, judgment}, ...scores]); // ← Ⓐ
}
// ・・・
これをConvex用に書き換えたのが以下のコードです。
- ① useQuery Hooksでデータベースから取得した値を
scores
変数に代入していますapi.scores.get
は後で説明するConvexデータベースの取得関数です- Convexデータベースが更新されると、このHooksがAppコンポーネントを再描画します
- useQueryはテーブルが空でデータが取得できない場合は
undefined
を戻すので?? []
で空配列にしています - また型アサーションで
ScoreType[]
型を指定しています
- ② useMutation Hooksでデータベース更新関数を取得し
createScore
変数に代入していますapi.scores.create
は後で説明するConvexデータベースの挿入関数です- 結果としてデータベースが更新されるので、Appコンポーネントは再描画されます
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
import { Judgment, Te, ScoreType } from "./JyankenType";
import JyankenBox from "./JyankenBox";
import ScoreBox from "./ScoreBox";
export default function App() {
const scores = (useQuery(api.scores.get) ?? []) as ScoreType[]; // ← ①
const createScore = useMutation(api.scores.create); // ← ②
const pon = (human: Te) => {
const computer = Math.floor(Math.random() * 3) as Te;;
const judgment = (computer - human + 3) % 3 as Judgment;;
createScore({human, computer, judgment});
}
return (
<div className="md:ml-8">
<h1 className="my-4 ml-4 text-3xl font-bold">じゃんけん ポン!</h1>
<div className="p-3 md:p-6 bg-white md:w-3/5">
<JyankenBox pon={pon} />
<ScoreBox scores={scores} />
</div>
</div>
)
}
convex/scores.ts
このファイルはConvexデータベースのscores
テーブルの操作関数の集まりです。
- ①
get
関数はscores
テーブルの全データを取得する関数です- 引数
args
は無いです
- 引数
- ② ORM的なメソッドチェインでデータが取得(SELECT)できます
- ConvexのデータベースはRDBではないのでSQLは使えません
- ③
create
関数はscores
テーブルにデータを挿入する関数です- 引数
args
は人間、コンピューターの手、勝敗で、いずれもJavaScriptのnumber型です - データベースの扱える型の情報はData Typesにあります
- 引数
- ④ これもORM的なメソッドですね
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
export const get = query({ // ← ①
args: {},
handler: async (ctx) => {
return await ctx.db.query("scores").order("desc").collect(); // ← ②
},
});
export const create = mutation({ // ← ③
args: { human: v.number(), computer: v.number(), judgment: v.number() },
handler: async (ctx, args) => {
await ctx.db.insert("scores", // ← ④
{ human: args.human, computer: args.computer, judgment: args.judgment });
},
});
Convex対応コードはこれだけです❗
Convexの使い方
Convexの使い方はReact Quickstartを見るとわかります、まずはこれを試してみると良いと思いますが、今回のアプリを作った手順を書いておきます。
Reactアプリを作る
ReactアプリはNext.jsやRemixなども使えますが、ここでは久しぶりにViteを使いました。
$ npm create vite@latest convex-jyanken -- --template react-ts
$ cd convex-jyanken
$ npm install
この後Tailwind CSSをインストールし、いつものジャンケンアプリのコードを書きました。
Convexにインストール
いよいよConvexです、Convexをインストールし実行すると、GitHub等での認証が行われConvexのアカウントが作成されます。
$ npm install convex
$ npx convex dev
上が終了するとConvexのアクセス情報が.env.local
に書かれます。この情報をプロジェクトにコピーする事で他のアプリでもつかえます。
.env.local
CONVEX_DEPLOYMENT=dev:xxxxxxxxx-yyyy # team: yuumi-yoshida, project: convex-app1
VITE_CONVEX_URL=https://xxxxxxxxx-yyyy .convex.cloud
convex/schema.ts
Convextに作成するテーブル定義ファイルを作成します。書き方はSchemasを参照してください。
ここではnumber(Float64)型のカラムを持つscores
テーブルの定義です。制限付きですが配列やオブジェクト型のカラムも作成できます。
指定してませんが、プライマリーキー(_id
)と作成日付(_creationTime
)カラムが自動的に作られます。_idはハッシュ文字列です。また、関連キーやインデックスも設定出来ます。
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
scores: defineTable({
human: v.number(),
computer: v.number(),
judgment: v.number(),
}),
});
convex/scores.ts
この後、最初に示したconvex/scores.ts
を作りました。書き方はReading Data、 Writing Dataを参考にしてください。
ドキュメントは少し不親切ですが、Convex DemosのGitHubも参考になります。 ネットを検索しても(英語を含めても)まだ情報は少ないですね。
Action
Convexは当然バックエンドにビジネスロジックなどの関数を作り、それをReactアプリから呼び出す事ができます。
ここではジャンケンを行うpon
関数をバックエンドに置いてみました、Convexではこのような関数をActionsと呼んでいます。
src/App.tsx
- ①
useAction
HooksでActionを呼出だすための関数をponに代入しています - ② Actionへの引数はオブジェクトなので、無名関数の中かから呼出しました
xport default function App() {
const pon = useAction(api.scores.pon); // ← ①
const scores = (useQuery(api.scores.get) ?? []) as ScoreType[];
return (
<div className="md:ml-8">
<h1 className="my-4 ml-4 text-3xl font-bold">じゃんけん ポン!</h1>
<div className="p-3 md:p-6 bg-white md:w-3/5">
<JyankenBox pon={(te) => pon({human: te})} /> {/* ← ② */}
<ScoreBox scores={scores} />
</div>
</div>
)
}
convex/scores.ts
Actionの定義はqueryやmutationと同様です。
- ① ジャンケンを行うActionの定義です
- 引数は人間の手で、型はnumberです
- 処理は概ね最初の
src/App.tsx
と同じです - ただし、Action内部からは直接データベース操作関数を呼び出せないので、内部関数(Internal Functions)を呼び出す必要があります
- ②
scores
テーブルにデータを挿入する内部関数ですmutation
ではなくinternalMutation
で定義しているだけで、最初に出てきたconvex/scores.ts
と同じです- 内部関数はクライアント(Reactアプリ)からは呼び出せません。クライアントから呼び出させるActionは認証等を行い、安全な環境内から内部関数と呼出すように作る必要があります(こではやってませんが)
- ③ Action内で内部関数の呼出しています
import { internal } from "./_generated/api";
import { query, internalMutation, action } from "./_generated/server";
import { v } from "convex/values";
export const get = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query("scores").order("desc").collect();
},
});
export const create = internalMutation({ // ← ②
args: { human: v.number(), computer: v.number(), judgment: v.number() },
handler: async (ctx, args) => {
await ctx.db.insert("scores",
{ human: args.human, computer: args.computer, judgment: args.judgment });
},
});
export const pon = action({ // ← ①
args: { human: v.number() },
handler: async (ctx, args) => {
const computer = Math.floor(Math.random() * 3);
const judgment = (computer - args.human + 3) % 3;
await ctx.runMutation(internal.scores.create, // ← ③
{ human: args.human, computer, judgment });
},
});
まとめ
Firebaseを使ったiOSアプリを作り今でも使っています。 しかし、Reactでフロントエンドを作るならバックエンドはFirebaseではなくConvexがお薦めです。
今回の例でもわかるように、とてもReactととの親和性が高くState管理をuseState
を、useQuery
, useMutation
, useAction
に置き換えてデータベースアクセスやロジックを書けばバックエンドが出来てしまいます。
もちろんConvexを仕事で使って大丈夫なのかは、Googleが運営するFirebaseに比べると不安はあるかも知れませんね・・・
ただしConvexはBackendも含めオープンソース(FSLライセンス)なので、最悪の場合は自前サーバーで運用すれば良さそうです。
リアルタイムDBが活かせるようなアプリがあったら使ってみたいですね。