convex-quickstart by get-convex/convex-agent-plugins
npx skills add https://github.com/get-convex/convex-agent-plugins --skill convex-quickstart在几分钟内搭建一个生产就绪的 Convex 后端。本技能将指导您初始化 Convex、创建数据模式、设置身份验证以及构建您的第一个 CRUD 操作。
开始之前,请验证:
node --version # v18 或更高版本
npm --version # v8 或更高版本
# 安装 Convex
npm install convex
# 初始化(创建 convex/ 目录)
npx convex dev
此命令:
convex/ 目录创建 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"]),
// 在此处添加您的表
// 示例:任务表
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"]),
});
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
我们将使用 WorkOS AuthKit,它提供了最简设置即可使用的完整身份验证解决方案。
npm install @workos-inc/authkit-react
// 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);
// 配置 Convex 使用 WorkOS 身份验证
convex.setAuth(useAuth);
function App() {
return (
<AuthKitProvider clientId={import.meta.env.VITE_WORKOS_CLIENT_ID}>
<ConvexProvider client={convex}>
<YourApp />
</ConvexProvider>
</AuthKitProvider>
);
}
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
# 对于 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 身份验证文档。
创建 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 ?? "",
});
},
});
创建 convex/tasks.ts:
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { getCurrentUser } from "./lib/auth";
// 列出当前用户的所有任务
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();
},
});
// 获取单个任务
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;
},
});
// 创建任务
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(),
});
},
});
// 更新任务
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);
},
});
// 删除任务
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);
},
});
// 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>
{/* 创建任务 */}
<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>
{/* 任务列表 */}
{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>
);
}
开发环境(请使用此命令!):
# 启动开发服务器(非生产环境!)
npx convex dev
# 此命令在本地运行,并在更改时自动重新加载
# 所有开发工作都使用此命令
生产环境部署:
# 仅在部署到生产环境时使用此命令!
npx convex deploy
# 警告:此命令将部署到您的生产环境
# 开发期间请勿使用
重要提示: 开发期间请始终使用 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);
}
},
});
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install convex @workos-inc/authkit-react
npx convex dev
npx create-next-app@latest my-app
cd my-app
npm install convex @workos-inc/authkit-nextjs
npx convex dev
npx create-expo-app my-app
cd my-app
npm install convex
npx convex dev
npm install convexnpx convex dev 正在运行(使用此命令,而非 deploy!)getCurrentUser 辅助函数npx convex deploy 部署到生产环境请记住: 所有开发工作都使用 npx convex dev。仅在部署到生产环境时使用 npx convex deploy!
完成快速入门后:
storeUser 变更函数getCurrentUser 是否正确导入npx convex dev(重新生成类型).withIndex() 而非 .filter()每周安装量
112
代码仓库
GitHub 星标数
89
首次出现
2026年2月7日
安全审计
安装于
codex95
claude-code91
opencode79
gemini-cli77
github-copilot76
cursor73
Get a production-ready Convex backend set up in minutes. This skill guides you through initializing Convex, creating your schema, setting up auth, and building your first CRUD operations.
Before starting, verify:
node --version # v18 or higher
npm --version # v8 or higher
# Install Convex
npm install convex
# Initialize (creates convex/ directory)
npx convex dev
This command:
convex/ directoryCreate 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"]),
});
We'll use WorkOS AuthKit, which provides a complete auth solution with minimal setup.
npm install @workos-inc/authkit-react
// 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>
);
}
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
Alternative auth providers: If you need to use a different provider (Clerk, Auth0, custom JWT), see the Convex auth documentation.
Create 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;
}
Create 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 ?? "",
});
},
});
Create 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);
},
});
// 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>
);
}
For Development (use this!):
# Start development server (NOT production!)
npx convex dev
# This runs locally and auto-reloads on changes
# Use this for all development work
For Production Deployment:
# ONLY use this when deploying to production!
npx convex deploy
# WARNING: This deploys to your production environment
# Don't use this during development
Important: Always use npx convex dev during development. Only use npx convex deploy when you're ready to ship to production.
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);
}
},
});
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install convex @workos-inc/authkit-react
npx convex dev
npx create-next-app@latest my-app
cd my-app
npm install convex @workos-inc/authkit-nextjs
npx convex dev
npx create-expo-app my-app
cd my-app
npm install convex
npx convex dev
npm install convex completednpx convex dev running (use this, NOT deploy!)getCurrentUser helper implementednpx convex deployRemember: Use npx convex dev for all development work. Only use npx convex deploy when deploying to production!
After quickstart:
storeUser mutation on first sign-ingetCurrentUser is imported correctlynpx convex dev (regenerates types).withIndex() instead of .filter()Weekly Installs
112
Repository
GitHub Stars
89
First Seen
Feb 7, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex95
claude-code91
opencode79
gemini-cli77
github-copilot76
cursor73
Azure Data Explorer (Kusto) 查询技能:KQL数据分析、日志遥测与时间序列处理
138,800 周安装
Vue 3 最佳实践指南:常见陷阱、性能优化与响应式编程技巧
12,800 周安装
Better Auth 身份验证技能指南:为 TypeScript/JavaScript 应用添加认证
13,300 周安装
图像压缩工具 - 使用 sips, cwebp, ImageMagick, Sharp 优化图片大小
13,400 周安装
AI 产品需求文档(PRD)生成指南:GitHub Copilot 技能,高效撰写技术规范
13,500 周安装
X转Markdown工具:将推文和文章转换为带元数据的Markdown格式
13,800 周安装
Vite 8 前端构建工具指南:配置、插件、SSR 与 Rolldown 迁移
14,600 周安装