mcpField
C
convex-quickstart

convex-quickstart

github.com/get-convex开发117219 次安装1 天前更新
代码仓库

关于 convex-quickstart

面向开发者的 Convex 后端入门技能,适用于 React、Next.js 或 Expo 项目,覆盖初始化、模式、身份验证、CRUD 和开发部署流程。

主要功能

  1. 01初始化 Convex 项目
  2. 02指导模式与身份验证配置
  3. 03展示 CRUD 查询和变更写法
  4. 04通过 React hooks 集成 Convex
  5. 05区分开发与生产命令

适用场景

  1. 01启动新的 Convex 应用
  2. 02为现有 React 应用添加 Convex
  3. 03用实时数据快速构建功能原型
  4. 04通过实际配置学习 Convex

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/get-convex/convex-agent-plugins --skill convex-quickstart

安装后在 Claude Code 中输入/convex-quickstart确认已加载。

详细介绍

Convex 快速入门

只需几分钟,即可设置可用于生产环境的 Convex 后端。本指南将引导你完成 Convex 初始化、创建架构、设置身份验证,以及构建首批 CRUD 操作。

适用场景

  • 使用 Convex 启动全新项目
  • 将 Convex 添加到现有的 React/Next.js 应用
  • 使用实时数据为新功能制作原型
  • 将其他后端迁移到 Convex
  • 首次向他人介绍 Convex

前置条件检查

开始之前,请先确认:

node --version  # v18 or higher
npm --version   # v8 or higher

快速入门流程

步骤 1:安装并初始化
# Install Convex
npm install convex

# Initialize (creates convex/ directory)
npx convex dev

此命令会:

  • 创建 convex/ 目录
  • 设置身份验证
  • 启动开发服务器
  • 生成 TypeScript 类型
步骤 2:创建架构

创建 convex/schema.ts:

import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    tokenIdentifier: v.string(),
    name: v.string(),
    email: v.string(),
  }).index("by_token", ["tokenIdentifier"]),

  // Add your tables here
  // Example: Tasks table
  tasks: defineTable({
    userId: v.id("users"),
    title: v.string(),
    completed: v.boolean(),
    createdAt: v.number(),
  })
    .index("by_user", ["userId"])
    .index("by_user_and_completed", ["userId", "completed"]),
});
步骤 3:设置身份验证

我们将使用 WorkOS AuthKit,它只需极少的设置即可提供完整的身份验证解决方案。

npm install @workos-inc/authkit-react
React/Vite 应用:
// src/main.tsx
import { AuthKitProvider, useAuth } from "@workos-inc/authkit-react";
import { ConvexReactClient } from "convex/react";
import { ConvexProvider } from "convex/react";

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);

// Configure Convex to use WorkOS auth
convex.setAuth(useAuth);

function App() {
  return (
    <AuthKitProvider clientId={import.meta.env.VITE_WORKOS_CLIENT_ID}>
      <ConvexProvider client={convex}>
        <YourApp />
      </ConvexProvider>
    </AuthKitProvider>
  );
}
Next.js 应用:
npm install @workos-inc/authkit-nextjs
// app/layout.tsx
import { AuthKitProvider } from "@workos-inc/authkit-nextjs";
import { ConvexClientProvider } from "./ConvexClientProvider";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <AuthKitProvider>
          <ConvexClientProvider>
            {children}
          </ConvexClientProvider>
        </AuthKitProvider>
      </body>
    </html>
  );
}
// app/ConvexClientProvider.tsx
"use client";

import { ConvexReactClient } from "convex/react";
import { ConvexProvider } from "convex/react";
import { useAuth } from "@workos-inc/authkit-nextjs";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: React.ReactNode }) {
  const { getToken } = useAuth();

  convex.setAuth(async () => {
    return await getToken();
  });

  return <ConvexProvider client={convex}>{children}</ConvexProvider>;
}
环境变量:
# .env.local
VITE_CONVEX_URL=https://your-deployment.convex.cloud
VITE_WORKOS_CLIENT_ID=your_workos_client_id

# For Next.js:
NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
NEXT_PUBLIC_WORKOS_CLIENT_ID=your_workos_client_id
WORKOS_API_KEY=your_workos_api_key
WORKOS_COOKIE_PASSWORD=generate_a_random_32_character_string

其他身份验证提供商: 如果你需要使用其他提供商(Clerk、Auth0、自定义 JWT),请参阅 Convex 身份验证文档。

步骤 4:创建身份验证辅助函数

创建 convex/lib/auth.ts:

import { QueryCtx, MutationCtx } from "../_generated/server";
import { Doc } from "../_generated/dataModel";

export async function getCurrentUser(
  ctx: QueryCtx | MutationCtx
): Promise<Doc<"users">> {
  const identity = await ctx.auth.getUserIdentity();
  if (!identity) {
    throw new Error("Not authenticated");
  }

  const user = await ctx.db
    .query("users")
    .withIndex("by_token", q =>
      q.eq("tokenIdentifier", identity.tokenIdentifier)
    )
    .unique();

  if (!user) {
    throw new Error("User not found");
  }

  return user;
}

创建 convex/users.ts:

import { mutation } from "./_generated/server";

export const store = mutation({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Not authenticated");

    const existing = await ctx.db
      .query("users")
      .withIndex("by_token", q =>
        q.eq("tokenIdentifier", identity.tokenIdentifier)
      )
      .unique();

    if (existing) return existing._id;

    return await ctx.db.insert("users", {
      tokenIdentifier: identity.tokenIdentifier,
      name: identity.name ?? "Anonymous",
      email: identity.email ?? "",
    });
  },
});
步骤 5:创建首批 CRUD 操作

创建 convex/tasks.ts:

import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { getCurrentUser } from "./lib/auth";

// List all tasks for current user
export const list = query({
  args: {},
  handler: async (ctx) => {
    const user = await getCurrentUser(ctx);
    return await ctx.db
      .query("tasks")
      .withIndex("by_user", q => q.eq("userId", user._id))
      .order("desc")
      .collect();
  },
});

// Get a single task
export const get = query({
  args: { taskId: v.id("tasks") },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    const task = await ctx.db.get(args.taskId);

    if (!task) throw new Error("Task not found");
    if (task.userId !== user._id) throw new Error("Unauthorized");

    return task;
  },
});

// Create a task
export const create = mutation({
  args: { title: v.string() },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    return await ctx.db.insert("tasks", {
      userId: user._id,
      title: args.title,
      completed: false,
      createdAt: Date.now(),
    });
  },
});

// Update a task
export const update = mutation({
  args: {
    taskId: v.id("tasks"),
    title: v.optional(v.string()),
    completed: v.optional(v.boolean()),
  },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    const task = await ctx.db.get(args.taskId);

    if (!task) throw new Error("Task not found");
    if (task.userId !== user._id) throw new Error("Unauthorized");

    const updates: any = {};
    if (args.title !== undefined) updates.title = args.title;
    if (args.completed !== undefined) updates.completed = args.completed;

    await ctx.db.patch(args.taskId, updates);
  },
});

// Delete a task
export const remove = mutation({
  args: { taskId: v.id("tasks") },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    const task = await ctx.db.get(args.taskId);

    if (!task) throw new Error("Task not found");
    if (task.userId !== user._id) throw new Error("Unauthorized");

    await ctx.db.delete(args.taskId);
  },
});
步骤 6:在 React 应用中使用
// app/tasks/page.tsx
"use client";

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

export default function TasksPage() {
  const tasks = useQuery(api.tasks.list);
  const create = useMutation(api.tasks.create);
  const update = useMutation(api.tasks.update);
  const remove = useMutation(api.tasks.remove);

  if (!tasks) return <div>Loading...</div>;

  return (
    <div>
      <h1>Tasks</h1>

      {/* Create task */}
      <form onSubmit={(e) => {
        e.preventDefault();
        const formData = new FormData(e.target as HTMLFormElement);
        create({ title: formData.get("title") as string });
        (e.target as HTMLFormElement).reset();
      }}>
        <input name="title" placeholder="New task" required />
        <button type="submit">Add</button>
      </form>

      {/* Task list */}
      {tasks.map(task => (
        <div key={task._id}>
          <input
            type="checkbox"
            checked={task.completed}
            onChange={(e) => update({
              taskId: task._id,
              completed: e.target.checked
            })}
          />
          <span>{task.title}</span>
          <button onClick={() => remove({ taskId: task._id })}>
            Delete
          </button>
        </div>
      ))}
    </div>
  );
}
步骤 7:开发环境与生产环境

开发环境(请使用此命令!):

# Start development server (NOT production!)
npx convex dev

# This runs locally and auto-reloads on changes
# Use this for all development work

生产环境部署:

# ONLY use this when deploying to production!
npx convex deploy

# WARNING: This deploys to your production environment
# Don't use this during development

重要提示: 开发期间请始终使用 npx convex dev。只有准备好发布到生产环境时,才使用 npx convex deploy。

常见模式

分页查询
export const listPaginated = query({
  args: {
    cursor: v.optional(v.string()),
    limit: v.number(),
  },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);

    const results = await ctx.db
      .query("tasks")
      .withIndex("by_user", q => q.eq("userId", user._id))
      .order("desc")
      .paginate({ cursor: args.cursor, limit: args.limit });

    return results;
  },
});
按状态筛选
export const listByStatus = query({
  args: {
    completed: v.boolean(),
  },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);

    return await ctx.db
      .query("tasks")
      .withIndex("by_user_and_completed", q =>
        q.eq("userId", user._id).eq("completed", args.completed)
      )
      .collect();
  },
});
定时任务
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

crons.daily(
  "cleanup-old-tasks",
  { hourUTC: 0, minuteUTC: 0 },
  internal.tasks.cleanupOld
);

export default crons;

// convex/tasks.ts
export const cleanupOld = internalMutation({
  handler: async (ctx) => {
    const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
    const oldTasks = await ctx.db
      .query("tasks")
      .filter(q =>
        q.and(
          q.eq(q.field("completed"), true),
          q.lt(q.field("createdAt"), thirtyDaysAgo)
        )
      )
      .collect();

    for (const task of oldTasks) {
      await ctx.db.delete(task._id);
    }
  },
});

项目模板

React + Vite + Convex
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install convex @workos-inc/authkit-react
npx convex dev
Next.js + Convex
npx create-next-app@latest my-app
cd my-app
npm install convex @workos-inc/authkit-nextjs
npx convex dev
Expo (React Native) + Convex
npx create-expo-app my-app
cd my-app
npm install convex
npx convex dev

检查清单

  • 已完成 npm install convex
  • npx convex dev 正在运行(请使用此命令,不要使用 deploy!)
  • 已创建包含适当索引的架构
  • 已配置身份验证提供商(WorkOS/自定义)
  • 已实现 getCurrentUser 辅助函数
  • 已创建用户存储 mutation
  • CRUD 操作已添加身份验证检查
  • 前端已集成 hooks
  • 已在本地测试查询和 mutation
  • 准备好用于生产环境时,请使用 npx convex deploy

请记住: 所有开发工作都请使用 npx convex dev。只有部署到生产环境时,才使用 npx convex deploy!

后续步骤

完成快速入门后:

  1. 向架构添加更多表
  2. 实现表之间的关系
  3. 添加文件存储(Convex 支持文件上传)
  4. 为 AI 功能设置向量搜索
  5. 为定时任务添加 cron 作业
  6. 配置生产环境变量

故障排除

“未通过身份验证”错误
  • 确保已配置身份验证提供商
  • 首次登录时调用 storeUser mutation
  • 检查 getCurrentUser 是否正确导入
类型未更新
  • 运行 npx convex dev(会重新生成类型)
  • 在编辑器中重启 TypeScript 服务器
查询缓慢
  • 在架构中添加索引
  • 使用 .withIndex() 代替 .filter()
  • 在仪表板中检查查询模式

了解更多

评论

支持 Markdown