convex-backend – skill Lập trình cho agent AI
Xây dựng backend reactive với hàm Convex, kiểm tra schema, tích hợp auth và quy trình triển khai. Dùng khi xây ứng dụng real-time với hàm server an toàn kiểu dữ liệu và bộ nhớ đệm tự động.
Cài skill convex-backend bằng một lệnh
Chọn công cụ bạn đang dùng, copy lệnh và dán vào terminal. Skill sẽ nằm trong thư mục skills của công cụ đó và sẵn sàng dùng ở phiên làm việc tiếp theo.
Claude Code
mkdir -p ~/.claude/skills && curl -fsSL https://skill.dn1nguoi.com/z/convex-backend-eb3e0.tgz | tar xz -C ~/.claude/skills
Gọi trong Claude Code: /convex-backend – rồi mô tả công việc bạn muốn làm.
Codex
mkdir -p ~/.codex/skills && curl -fsSL https://skill.dn1nguoi.com/z/convex-backend-eb3e0.tgz | tar xz -C ~/.codex/skills
Gọi trong Codex: $convex-backend – rồi mô tả công việc bạn muốn làm.
Hermes Agent
mkdir -p ~/.hermes/skills && curl -fsSL https://skill.dn1nguoi.com/z/convex-backend-eb3e0.tgz | tar xz -C ~/.hermes/skills
Gọi trong Hermes Agent: /convex-backend – rồi mô tả công việc bạn muốn làm.
DeepSeek
mkdir -p ~/.deepcode/skills && curl -fsSL https://skill.dn1nguoi.com/z/convex-backend-eb3e0.tgz | tar xz -C ~/.deepcode/skills
Gọi trong DeepSeek: /convex-backend – rồi mô tả công việc bạn muốn làm.
Nội dung SKILL.md
Nội dung SKILL.md gốc bằng tiếng Anh.
Convex Backend
Use Convex to build type-safe backend logic with realtime data sync.
When to Use This Skill
Use this skill when:
- Building real-time collaborative apps (chat, dashboards, multiplayer)
- Need a backend with zero infrastructure management
- Want type-safe server functions with automatic caching
- Building AI apps that need reactive data (agent status, streaming results)
- Prototyping quickly with a managed database + functions
Prerequisites
- Node.js 18+
- npm or pnpm
- Convex account (free tier: 1M function calls/month)
Quick Start
# Initialize Convex in an existing project
npm install convex
npx convex dev # Start local development (syncs with cloud)
# In a new project
npm create convex@latestSchema Definition
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
users: defineTable({
name: v.string(),
email: v.string(),
role: v.union(v.literal("admin"), v.literal("member")),
avatarUrl: v.optional(v.string()),
createdAt: v.number(),
})
.index("by_email", ["email"])
.index("by_role", ["role"]),
messages: defineTable({
userId: v.id("users"),
channelId: v.id("channels"),
body: v.string(),
attachments: v.optional(v.array(v.string())),
createdAt: v.number(),
})
.index("by_channel", ["channelId", "createdAt"])
.index("by_user", ["userId"]),
channels: defineTable({
name: v.string(),
description: v.optional(v.string()),
isPrivate: v.boolean(),
}),
});Security completeness
The snippets below omit the application membership/ownership model and are not deployable authorization examples. Implement a shared server-side access check before listByChannel, send, paid generateResponse, upload URL generation and file URL reads. Validate identity using a stable provider subject/token identifier, not an unchecked/non-unique email. Bound body/prompt/limit values, reject missing channels and cross-tenant IDs, check external HTTP status/content shape and avoid duplicate paid requests. An isPrivate field alone enforces nothing. Use an approved dev deployment; convex dev may synchronize to cloud. Verify current quotas/pricing rather than assuming the stated free allowance.
Queries (Real-Time Reads)
// convex/messages.ts
import { query } from "./_generated/server";
import { v } from "convex/values";
export const listByChannel = query({
args: {
channelId: v.id("channels"),
limit: v.optional(v.number()),
},
handler: async (ctx, args) => {
const messages = await ctx.db
.query("messages")
.withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
.order("desc")
.take(args.limit ?? 50);
// Resolve user data for each message
return Promise.all(
messages.map(async (msg) => {
const user = await ctx.db.get(msg.userId);
return { ...msg, user: user ? { name: user.name, avatarUrl: user.avatarUrl } : null };
})
);
},
});Mutations (Writes)
// convex/messages.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const send = mutation({
args: {
channelId: v.id("channels"),
body: v.string(),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
// Find or create user
const user = await ctx.db
.query("users")
.withIndex("by_email", (q) => q.eq("email", identity.email!))
.unique();
if (!user) throw new Error("User not found");
return await ctx.db.insert("messages", {
userId: user._id,
channelId: args.channelId,
body: args.body,
createdAt: Date.now(),
});
},
});Actions (External APIs, AI)
// convex/ai.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { api } from "./_generated/api";
export const generateResponse = action({
args: { prompt: v.string(), channelId: v.id("channels") },
handler: async (ctx, args) => {
// Call external AI API
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.ANTHROPIC_API_KEY!,
"anthropic-version": "2023-06-01",
},
body: JSON.stringify({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: args.prompt }],
}),
});
const data = await response.json();
const aiMessage = data.content[0].text;
// Save AI response as a message via mutation
await ctx.runMutation(api.messages.send, {
channelId: args.channelId,
body: aiMessage,
});
return aiMessage;
},
});Scheduled Functions (Cron Jobs)
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// Run every hour
crons.interval("cleanup old messages", { hours: 1 }, internal.maintenance.cleanupOldMessages);
// Run daily at midnight UTC
crons.cron("daily report", "0 0 * * *", internal.reports.generateDailyReport);
export default crons;Auth Integration
// convex/auth.config.ts
export default {
providers: [
{
domain: process.env.AUTH_DOMAIN,
applicationID: "convex",
},
],
};// React client setup
import { ConvexProviderWithClerk } from "convex/react-clerk";
import { ClerkProvider, useAuth } from "@clerk/clerk-react";
function App() {
return (
<ClerkProvider publishableKey={CLERK_KEY}>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
<MyApp />
</ConvexProviderWithClerk>
</ClerkProvider>
);
}React Client Usage
// src/components/Chat.tsx
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
import type { Id } from "../convex/_generated/dataModel";
export function Chat({ channelId }: { channelId: Id<"channels"> }) {
// Real-time query — auto-updates when data changes
const messages = useQuery(api.messages.listByChannel, { channelId });
const sendMessage = useMutation(api.messages.send);
const handleSend = async (body: string) => {
await sendMessage({ channelId, body });
};
if (messages === undefined) return <div>Loading...</div>;
return (
<div>
{messages.map((msg) => (
<div key={msg._id}>
<strong>{msg.user?.name}</strong>: {msg.body}
</div>
))}
</div>
);
}Deployment
# Deploy to production
npx convex deploy
# Deploy with environment variables
npx convex deploy --env-file .env.production
# Set environment variables
npx convex env set ANTHROPIC_API_KEY sk-ant-...
npx convex env list
# View logs
npx convex logs
npx convex logs --follow
# Run a function manually
npx convex run messages:listByChannel '{"channelId": "abc123"}'File Storage
// convex/files.ts
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
export const generateUploadUrl = mutation(async (ctx) => {
return await ctx.storage.generateUploadUrl();
});
export const getFileUrl = query({
args: { storageId: v.id("_storage") },
handler: async (ctx, args) => {
return await ctx.storage.getUrl(args.storageId);
},
});Best Practices
- Define schema and validation before writing functions
- Keep mutations idempotent where possible
- Use auth identity checks in every privileged query/mutation
- Add indexes early for high-read collections
- Use
internalfunctions for server-only logic (crons, webhooks) - Store secrets in Convex environment variables, never in code
- Use optimistic updates in the React client for instant UI feedback
Troubleshooting
| Issue | Solution |
|-------|---------|
| Function timeout | Actions have 10min limit; break into smaller steps |
| Query too slow | Add database index matching your query pattern |
| Type errors | Run npx convex dev to regenerate types |
| Auth not working | Check auth.config.ts and provider domain |
| Deploy fails | Check npx convex logs, verify env vars are set |
Related Skills
- firebase-app-platform (
firebase-app-platform) — Alternative managed backend - vercel-deployments (
vercel-deployments) — Frontend hosting - agent-observability (
agent-observability) — Instrument AI-driven backend flows
Limitations
- Infrastructure commands can disrupt services: confirm target host/scope and have backups/snapshots before mutating state.
- Docs-only import: upstream scripts and templates not bundled.
Skill liên quan
- copilot-delegate – Chỉ giao tác vụ viết code cho GitHub Copilot CLI (`copilot`) khi người dùng yêu cầu rõ…
- copilot-sdk – Xây dựng ứng dụng tương tác với GitHub Copilot theo cách lập trình. SDK bao bọc Copilot…
- screen-reader-testing – Kiểm thử ứng dụng web với trình đọc màn hình gồm VoiceOver, NVDA và JAWS. Dùng khi kiểm…
- cowork-to-code-bridge – Dùng cầu nối cowork-to-code đã cài sẵn và được xác minh độc lập để chạy các hành động…
- cpp – Hướng dẫn super-code riêng cho ngôn ngữ cpp.
- create-branch – Tạo nhánh git theo quy ước đặt tên của Sentry. Dùng khi được yêu cầu "create a branch"…
- requesting-code-review – Review trước khi commit: quét bảo mật, cổng chất lượng và tự động sửa lỗi.
- rowan – Rowan là nền tảng cloud-native cho quy trình mô hình hóa phân tử và hóa dược, có Python…