👻

【これはヤバい】Convexを試したらあらゆる管理が不要になる未来が見えた

に公開

なぜオレはあんな無駄な時間(数週間で AI を駆使して知見のないものを含めた使用技術の検証と α 版のリリースに死力を尽くした)を...

本記事では Next.js + Vercel + Convex を想定して試したので,開発体験の素晴らしさと Convex のヤバさについて紹介する.

Convex は Next.js 以外にもサポートしているが,Vercel との親和性など考えると特にこだわりがなければ Next.js を使うのが良さそう.個人的に Rust に対応しているのがアツいのでどこかで試したい.

Convex とは

グリンガムの鞭.

超強い BaaS.DB,ストレージ,リアルタイム通信,サーバ実行関数など必要なものが統合されている.DB がメインではあるが,バックエンドというより開発体験を変えるレベル.

DB の内部は AWS の RDS(MySQL)で動いているらしい(ドキュメントによる).

無料プランもあるが運用するなら有料プラン($25 / 月)を使ったほうがよさそう(後述).

Convex の機能と強さ

考え方

Convex ではバックエンド関連の処理が全て統合され,TypeScript で実装することができる.

例えば,テーブルの定義や操作を全て TypeScript で行う.下記は messages テーブルの定義とデータ挿入,データ取得の例.該当の TypeScript を変更すると DB のスキーマが自動で更新される.マイグレーションの管理とか不要.

export const sendMessage = mutation({
  args: {
    user: v.string(),
    body: v.string(),
  },
  handler: async (ctx, args) => {
    await ctx.db.insert("messages", {
      user: args.user,
      body: args.body,
    });
  },
});

export const getMessages = query({
  args: {},
  handler: async (ctx) => {
    const messages = await ctx.db.query("messages").order("desc").take(50);
    return messages.reverse();
  },
});

型安全

データの型安全が保証されており,フロントエンドからのクエリやミューテーションも TypeScript で型チェックされる.これにより実行時エラーの可能性が大幅に減少する.開発者も助かる.

// 従来のREST API
const response = await fetch("/api/users");
const users = await response.json(); // any型、実行時エラーの可能性

// Convex
// 完全な型安全性がフロントエンドまで自動伝播
export const getMessages = query({
  args: { userId: v.id("users") },
  handler: async (ctx, args) => {
    return ctx.db
      .query("messages")
      .filter((q) => q.eq(q.field("userId"), args.userId))
      .collect();
  },
});

// フロントエンド側で自動的に型推論
const messages = useQuery(api.messages.getMessages, { userId }); // Message[]型

リアルタイム通信

Convex はリアルタイム通信をサポートしておりデータの変更が即座にフロントエンドに反映される.自前で WebSocket や Polling を実装する必要がない.

また,リアルタイム通信をサポートする Firebase と比較しても簡潔さが際立つ.ヤバい.

// Firebase
const [messages, setMessages] = useState([]);
useEffect(() => {
  const unsubscribe = onSnapshot(collection(db, "messages"), (snapshot) => {
    setMessages(snapshot.docs.map((doc) => doc.data()));
  });
  return unsubscribe;
}, []);

// Convex
const messages = useQuery(api.messages.list);
// 自動リアルタイム更新、型安全性、最適化済み

トランザクション

NoSQL ではトランザクションが難しいことが多いが,Convex ではトランザクションを簡単に扱える.複数のデータ操作を一つのトランザクションとしてまとめて実行できる.

特別な処理を書く必要なく,handler 内で複数の DB 操作を行うだけでトランザクションが保証される.安心安全.

export const transferMoney = mutation({
  args: { fromId: v.id("users"), toId: v.id("users"), amount: v.number() },
  handler: async (ctx, args) => {
    // 全体が1つのトランザクションで実行
    const from = await ctx.db.get(args.fromId);
    const to = await ctx.db.get(args.toId);

    await ctx.db.patch(args.fromId, { balance: from.balance - args.amount });
    await ctx.db.patch(args.toId, { balance: to.balance + args.amount });
    // 自動的にトランザクション保証
  },
});

Convex Actions

Convex 上で指定した関数を実行する機能.Functions みたいな感じだけどシームレスにコードを書けるので非常に使いやすい.データベース操作(Queries/Mutations)とは異なり、外部 API 呼び出しや副作用のある処理が可能.

例えば,csv ファイルをアップロードして AI になにかさせる場合など Convex Actions で実行するといい感じにまとまりそう.

Convex Actions は 10 分が上限だが,大規模な処理を行わなければ十分な時間なので多くのプロジェクトで活用できそう.ほぼこれで良いのでは.

// 1. Vercel API Route: ファイル受信と軽量処理
export async function POST(request: Request) {
  const formData = await request.formData();
  const csvFile = formData.get("file") as File;
  const comment = formData.get("comment") as string;
  const date = formData.get("date") as string;

  // ファイルをConvex Storageに保存
  const fileId = await convex.mutation(api.files.upload, {
    file: await csvFile.arrayBuffer(),
    filename: csvFile.name,
  });

  // ジョブを作成
  const jobId = await convex.mutation(api.jobs.create, {
    fileId,
    comment,
    date,
    status: "pending",
  });

  // Convex Actionで非同期処理開始
  await convex.action(api.analysis.processFile, { jobId });

  return Response.json({ jobId, message: "処理を開始しました" });
}

// 2. Convex Action: 重い処理(CSV解析+AI分析)
export const processFile = action({
  args: { jobId: v.id("jobs") },
  handler: async (ctx, args) => {
    // CSV読み込み・解析
    // 統計計算
    // AI分析
    // 結果保存
    // メール送信
  },
});

インフラを考えない構成

バックエンドのインフラ自体が Convex が管理してくれるため,開発者はアプリケーションのロジックに集中できる.最初に設定しておけばあとは指定のブランチにコードをマージすれば自動でデプロイされる(後述).放置万歳.必死こいて cdk deploy して GitHub Actions のワークフロー書いていた自分の立場 is 何処...

# 従来
- データベース設定・調整
- スケーリング設定
- バックアップ・復旧
- セキュリティパッチ
- 監視・アラート
- ロードバランサ設定

# Convex
- なし
- Vercelに環境変数を設定する程度

気になって試した点

開発フロー

ステージング環境などできるかどうか.

→ 基本は本番と各開発者用のクラウド環境(ローカルでも動かせる).

→ 有料プランを用いると preview 環境ができてブランチと連携して動く.develop ブランチにマージすると自動でデプロイされる,など.

→ 各環境で DB を用意するとそれぞれ接続用のキーを発行できる.Vercel でつくった各環境の環境変数にキーを設定する感じ.

テーブル定義の変更

テーブル定義の変更は TypeScript のコードを変更するだけで,マイグレーションの管理などは不要.

ただし,既存データと衝突する場合は変更に失敗する.追加カラムを option にして既存データ修正 → 必須にする,などで対応できそう.

id の扱い

個人的に好きなポイント.Convex では id は数字ではなく文字列(ULID みたいな感じ)で管理される.

id は実行時において文字列だが,Id 型を使用することでコンパイル時に ID を他の文字列と区別することができる.便利.

ログ・アラートなど

Convex のダッシュボードで確認可能.Webhook も設定できそう(試してない).

バックアップ

ダッシュボードで手動バックアップ可能.有料プランで自動バックアップも可能.

その他

モバイルと組み合わせる場合は下記のようになりそう?

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Swift App     │    │   Next.js App   │    │   Convex        │
│                 │    │                 │    │                 │
│ ┌─────────────┐ │    │ ┌─────────────┐ │    │ ┌─────────────┐ │
│ │ API Client  │─┼────┼▶│ API Routes  │─┼────┼▶│ Actions     │ │
│ └─────────────┘ │    │ └─────────────┘ │    │ │ Queries     │ │
│ ┌─────────────┐ │    │ ┌─────────────┐ │    │ │ Mutations   │ │
│ │ ConvexSDK   │─┼────┼─┼─────────────┼─┼────┼▶│ Storage     │ │
│ └─────────────┘ │    │ │   Web UI    │ │    │ └─────────────┘ │
└─────────────────┘    │ └─────────────┘ │    └─────────────────┘
                       └─────────────────┘

まとめ

バックエンドの世代が動いた感ある.Convex は開発体験を大幅に向上させる強力なツール.

特に TypeScript を使っている開発者にとっては,型安全なデータ操作やリアルタイム通信の簡潔さが魅力的.コードも少なくて済むしインフラの管理やワークフローの構築から開放されるのが非常に大きい.Rust は今後試す.

以上だ( `・ω・)b

GitHubで編集を提案

Discussion