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/■■■■■■■■■■■■

ここでは,DBの中身を閲覧することができます.S◯pabase感あります.
使ってみる
簡単にTODOアプリを作ってみます.
テンプレ消す
とりあえずテンプレートを消して,以下のようにします.
@import "tailwindcss";
"use client";
export default function Home() {
return (
<>
<div>TODO</div>
</>
);
}
タスク追加機能
新たにDB操作を行うための関数をconvexディレクトリ内に書いていきます.
はじめに,新しく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,
});
},
});
addTaskはmutationに各種設定を渡してできたmutation関数で,それをexportしているという感じがわかりやすいですね.mutation関数は1つのトランザクションとして振る舞い,エラー発生時はロールバックされるとのことです.
また,ctx.db.insertでInsertしていることがわかります.
これをフロントで使い,TODO作成機能を実装します.(面倒だったのでformは使っていません)
"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"が必要です.サーバコンポーネントは現時点ではベータ対応とのことでした.

見た目は微妙ですが,実際にhelloと打ち込んでみると,実際に機能することがわかります.
ダッシュボードにアクセスすると,きちんとDBに保存されています!
タスク一覧機能
タスク一覧も簡単です.
先程のconv.tsにqueryを追記していきます.データ作成ではmutationを利用しましたが,取得ではqueryを使っています.
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_doneがfalseであるものだけを取得するようにしてみました.
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>
+ ))}
</>
);
}
見た目は微妙ですがうまく動いています.
完了ボタン
最後に完了ボタンを実装してみたいと思います.
完了のロジックにもmutationが使われます.
データの追加,変更,削除にはmutation,取得にはqueryが使われます.
https://docs.convex.dev/database/writing-data
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 });
+ },
+ });
"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操作は,タスク作成とほとんど同じように書けます.
上記のコードで,完了ボタンのロジックが組めました.非常に簡単です.
まとめ
convexをNext.js(ほぼReact)で使ってみました.
リアルタイムなアプリケーションをサクサクと開発できました.
特にTypeScriptとの相性は抜群で,個人的にはFirebaseよりも扱いやすいと感じました.
今後は,Next.jsとの相性を知るためにサーバコンポーネントでの利用や,Authにも触れてみたいと思います.



