1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

convexをNext.jsで使ってみた

1
Last updated at Posted at 2025-10-02

convexなるものを見つけたのでNext.jsで使ってみました.Next.jsでセットアップしただけなので,ほぼReactで使った感じの話です.

環境

  • Windows 11 24H2
  • Node.js v22.16.0
  • npm 10.9.2

convexとは

公式では以下のように説明されています.

Convexはオープンソースのリアクティブデータベースです。クエリはデータベース内で直接実行されるTypeScriptコードです。Reactコンポーネントが状態の変化に反応するのと同様に、Convexクエリはデータベースの変更に反応します。
https://docs.convex.dev/home

いったんReactで触ってみた感じ,以下のような特徴があると感じました.

  • リアルタイムなデータベース
  • ORMのようなコードでDB操作できる
  • ReactはHooksを使っていて,シンプルなインテグレーション

公式チュートリアルがわかりやすいのですぐに使えると思います.

インストール+セットアップ

Next.jsの場合

npm create convex@latest

を打てば一発でセットアップされるようです.

npm run dev

いつものnpm run devを実行すると,初回実行時は以下のようなログイン表示が出ます.

? Welcome to Convex! Would you like to login to your account?
❯ Start without an account (run Convex locally)
  Login or create an account

このままアカウントを作らずに続行する事もできます.この場合,ローカルで実行するためにいろいろインストールされるようです.

ここで,Login or create an accountを選択すると,ログイン認証が行えます.現時点(2025-10-03)では,GoogleとGitHubログインが選択できました.

? Device name: ■■■■■
Visit https://auth.convex.dev/device?user_code=■■■■■ to finish logging in.
You should see the following code which expires in 299 seconds: ■■■■■■
? Open the browser? Yes
✔ Saved credentials to C:\Users\■■■■\.convex\config.json
? Do you agree to the Terms of Service at https://convex.dev/legal/v2022-03-02/tos Yes
? Project name: ■■■■■
✔ Created project ■■■■■, manage it at https://dashboard.convex.dev/t/■■■■■■/■■■■■■
✔ Provisioned a dev deployment and saved its:
    name as CONVEX_DEPLOYMENT to .env.local
    URL as NEXT_PUBLIC_CONVEX_URL to .env.local

Write your Convex functions in convex\
Give us feedback at https://convex.dev/community or support@convex.dev
View the Convex dashboard at https://dashboard.convex.dev/d/■■■■■■■■■■■■

✔ 00:31:52 Convex functions ready! (5.71s)

どうやら,ログインを行うと自動でクラウド上でプロビジョニングされるようです.楽ですね!

Dashboard

プロビジョニングが成功すると,dashboardを確認できます.

View the Convex dashboard at https://dashboard.convex.dev/d/■■■■■■■■■■■■

image.png
ここでは,DBの中身を閲覧することができます.S◯pabase感あります.

使ってみる

簡単にTODOアプリを作ってみます.

テンプレ消す

とりあえずテンプレートを消して,以下のようにします.

globals.css
@import "tailwindcss";
page.tsx
"use client";

export default function Home() {
  return (
    <>
      <div>TODO</div>      
    </>
  );
}

タスク追加機能

新たにDB操作を行うための関数をconvexディレクトリ内に書いていきます.
はじめに,新しくtsファイルを作成します.今回は名前をconv.tsにしてみました.

conv.ts
import { v } from "convex/values";
import { mutation } from "./_generated/server";

export const addTask = mutation({
  args: {
    taskName: v.string(),
    isDone: v.boolean(),
  },
  handler: async (ctx, args) => {
    await ctx.db.insert("my_tasks", {
      name: args.taskName,
      is_done: args.isDone,
    });
  },
});

addTaskmutationに各種設定を渡してできたmutation関数で,それをexportしているという感じがわかりやすいですね.mutation関数は1つのトランザクションとして振る舞い,エラー発生時はロールバックされるとのことです.

また,ctx.db.insertでInsertしていることがわかります.

これをフロントで使い,TODO作成機能を実装します.(面倒だったのでformは使っていません)

page.tsx
"use client";

import { useMutation, useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
import React from "react";

export default function Home() {
  const [taskName, setTaskName] = React.useState("");
  const createTask = useMutation(api.conv.addTask);

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    setTaskName(e.target.value);
  }

  async function handleClick() {
    await createTask({ taskName: taskName, isDone: false });
    setTaskName("");
  }

  return (
    <>
      <div>TODO</div>
      <input
        onChange={handleChange}
        value={taskName}
        className="border"
      ></input>
      <button onClick={handleClick} className="border">
        作成
      </button>
    </>
  );
}

Hooksを用いるので,"use client"が必要です.サーバコンポーネントは現時点ではベータ対応とのことでした.

image.png
見た目は微妙ですが,実際にhelloと打ち込んでみると,実際に機能することがわかります.

ダッシュボードにアクセスすると,きちんとDBに保存されています!

image.png

タスク一覧機能

タスク一覧も簡単です.

先程のconv.tsqueryを追記していきます.データ作成ではmutationを利用しましたが,取得ではqueryを使っています.

conv.ts
import { v } from "convex/values";
import { mutation, query } from "./_generated/server";

export const addTask = mutation({
  args: {
    taskName: v.string(),
    isDone: v.boolean(),
  },
  handler: async (ctx, args) => {
    await ctx.db.insert("my_tasks", {
      name: args.taskName,
      is_done: args.isDone,
    });
  },
});

+ export const getTask = query({
+   args: {},
+   handler: async (ctx) => {
+     const tasks = await ctx.db
+       .query("my_tasks")
+       .filter((q) => q.eq(q.field("is_done"), false))
+       .take(10);
+     return tasks;
+   },
+ });

この実装では,is_donefalseであるものだけを取得するようにしてみました.
SQLのWHERE句に相当するものはfilterまたはindexesのようです.
https://docs.convex.dev/database/reading-data/filters

"use client";

import { useMutation, useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
import React from "react";

export default function Home() {
  const [taskName, setTaskName] = React.useState("");
  const createTask = useMutation(api.conv.addTask);
+  const tasks = useQuery(api.conv.getTask);

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    setTaskName(e.target.value);
  }

  async function handleClick() {
    await createTask({ taskName: taskName, isDone: false });
    setTaskName("");
  }
  
  return (
    <>
      <div>TODO</div>
      <input
        onChange={handleChange}
        value={taskName}
        className="border"
      ></input>
      <button onClick={handleClick} className="border">
        作成
      </button>
+     {tasks?.map((task, index) => (
+       <div className="p-2 border my-1">{task.name}</div>
+     ))}
    </>
  );
}

見た目は微妙ですがうまく動いています.

image.png

完了ボタン

最後に完了ボタンを実装してみたいと思います.

完了のロジックにもmutationが使われます.
データの追加,変更,削除にはmutation,取得にはqueryが使われます.
https://docs.convex.dev/database/writing-data

conv.ts
import { v } from "convex/values";
import { mutation, query } from "./_generated/server";

export const addTask = mutation({
  args: {
    taskName: v.string(),
    isDone: v.boolean(),
  },
  handler: async (ctx, args) => {
    await ctx.db.insert("my_tasks", {
      name: args.taskName,
      is_done: args.isDone,
    });
  },
});

export const getTask = query({
  args: {},
  handler: async (ctx) => {
    const tasks = await ctx.db
      .query("my_tasks")
      .filter((q) => q.eq(q.field("is_done"), false))
      .take(10);
    return tasks;
  },
});

+ export const changeTaskStatus = mutation({
+   args: { id: v.id("my_tasks"), done: v.boolean() },
+   handler: async (ctx, args) => {
+     await ctx.db.patch(args.id, { is_done: args.done });
+   },
+ });

page.tsx
"use client";

import { useMutation, useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
import React from "react";
import { Id } from "@/convex/_generated/dataModel";

export default function Home() {
  const [taskName, setTaskName] = React.useState("");
  const createTask = useMutation(api.conv.addTask);
+  const changeTaskStatus = useMutation(api.conv.changeTaskStatus);
  const tasks = useQuery(api.conv.getTask);

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    setTaskName(e.target.value);
  }

  async function handleClick() {
    await createTask({ taskName: taskName, isDone: false });
    setTaskName("");
  }

+  async function handleDone(id: Id<"my_tasks">) {
+    await changeTaskStatus({ id: id, done: true });
+  }

  return (
    <>
      <div>TODO</div>
      <input
        onChange={handleChange}
        value={taskName}
        className="border"
      ></input>
      <button onClick={handleClick} className="border">
        作成
      </button>
      {tasks?.map((task, index) => (
-      <div className="p-2 border my-1">{task.name}</div>
+        <div className="flex justify-between p-2 border my-1">
+          <div>{task.name}</div>
+          <button onClick={() => handleDone(task._id)}>完了</button>
+        </div>
      ))}
    </>
  );
}

DB操作は,タスク作成とほとんど同じように書けます.

image.png
image.png

上記のコードで,完了ボタンのロジックが組めました.非常に簡単です.

まとめ

convexをNext.js(ほぼReact)で使ってみました.
リアルタイムなアプリケーションをサクサクと開発できました.

特にTypeScriptとの相性は抜群で,個人的にはFirebaseよりも扱いやすいと感じました.

今後は,Next.jsとの相性を知るためにサーバコンポーネントでの利用や,Authにも触れてみたいと思います.

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?