mit einem Klick
function-creator
// Create Convex queries, mutations, and actions with proper validation, authentication, and error handling. Use when implementing new API endpoints.
// Create Convex queries, mutations, and actions with proper validation, authentication, and error handling. Use when implementing new API endpoints.
Set up Convex authentication with proper user management, identity mapping, and access control patterns. Use when implementing auth flows.
Guide to using Convex components for feature encapsulation. Learn about sibling components, creating your own, and when to use components vs monolithic code.
Discover and use convex-helpers utilities for relationships, filtering, sessions, custom functions, and more. Use when you need pre-built Convex patterns.
Initialize a new Convex backend from scratch with schema, auth, and basic CRUD operations. Use when starting a new project or adding Convex to an existing app.
Plan and execute Convex schema migrations safely, including adding fields, creating tables, and data transformations. Use when schema changes affect existing data.
Design and generate Convex database schemas with proper validation, indexes, and relationships. Use when creating schema.ts or modifying table definitions.
| name | function-creator |
| description | Create Convex queries, mutations, and actions with proper validation, authentication, and error handling. Use when implementing new API endpoints. |
Generate secure, type-safe Convex functions following all best practices.
import { query } from "./_generated/server";
import { v } from "convex/values";
export const getTask = query({
args: { taskId: v.id("tasks") },
returns: v.union(v.object({
_id: v.id("tasks"),
text: v.string(),
completed: v.boolean(),
}), v.null()),
handler: async (ctx, args) => {
return await ctx.db.get(args.taskId);
},
});
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const createTask = mutation({
args: {
text: v.string(),
priority: v.optional(v.union(
v.literal("low"),
v.literal("medium"),
v.literal("high")
)),
},
returns: v.id("tasks"),
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
return await ctx.db.insert("tasks", {
text: args.text,
priority: args.priority ?? "medium",
completed: false,
createdAt: Date.now(),
});
},
});
ctx.runMutation"use node" directive when needing Node.js APIsImportant: If your action needs Node.js-specific APIs (crypto, third-party SDKs, etc.), add "use node" at the top of the file. Files with "use node" can ONLY contain actions, not queries or mutations.
"use node"; // Required for Node.js APIs like OpenAI SDK
import { action } from "./_generated/server";
import { api } from "./_generated/api";
import { v } from "convex/values";
import OpenAI from "openai";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export const generateTaskSuggestion = action({
args: { prompt: v.string() },
returns: v.string(),
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
// Call OpenAI (requires "use node")
const completion = await openai.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: args.prompt }],
});
const suggestion = completion.choices[0].message.content;
// Write to database via mutation
await ctx.runMutation(api.tasks.createTask, {
text: suggestion,
});
return suggestion;
},
});
Note: If you only need basic fetch (no Node.js APIs), you can omit "use node". But for third-party SDKs, crypto, or other Node.js features, you must use it.
Always define args with validators:
args: {
id: v.id("tasks"),
text: v.string(),
count: v.number(),
enabled: v.boolean(),
tags: v.array(v.string()),
metadata: v.optional(v.object({
key: v.string(),
})),
}
Always define returns:
returns: v.object({
_id: v.id("tasks"),
text: v.string(),
})
// Or for arrays
returns: v.array(v.object({ /* ... */ }))
// Or for nullable
returns: v.union(v.object({ /* ... */ }), v.null())
Always verify auth in public functions:
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}
Always verify ownership/permissions:
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");
}
export const getMyTasks = query({
args: {
status: v.optional(v.union(
v.literal("active"),
v.literal("completed")
)),
},
returns: v.array(v.object({
_id: v.id("tasks"),
text: v.string(),
completed: v.boolean(),
})),
handler: async (ctx, args) => {
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");
let query = ctx.db
.query("tasks")
.withIndex("by_user", q => q.eq("userId", user._id));
const tasks = await query.collect();
if (args.status) {
return tasks.filter(t =>
args.status === "completed" ? t.completed : !t.completed
);
}
return tasks;
},
});
export const updateTask = mutation({
args: {
taskId: v.id("tasks"),
text: v.optional(v.string()),
completed: v.optional(v.boolean()),
},
returns: v.id("tasks"),
handler: async (ctx, args) => {
// 1. Authentication
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
// 2. Get user
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");
// 3. Get resource
const task = await ctx.db.get(args.taskId);
if (!task) throw new Error("Task not found");
// 4. Authorization
if (task.userId !== user._id) {
throw new Error("Unauthorized");
}
// 5. Update
const updates: Partial<typeof task> = {};
if (args.text !== undefined) updates.text = args.text;
if (args.completed !== undefined) updates.completed = args.completed;
await ctx.db.patch(args.taskId, updates);
return args.taskId;
},
});
Create separate file for actions that need Node.js:
// convex/taskActions.ts
"use node"; // Required for SendGrid SDK
import { action } from "./_generated/server";
import { api } from "./_generated/api";
import { v } from "convex/values";
import sendgrid from "@sendgrid/mail";
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
export const sendTaskReminder = action({
args: { taskId: v.id("tasks") },
returns: v.boolean(),
handler: async (ctx, args) => {
// 1. Auth
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
// 2. Get data via query
const task = await ctx.runQuery(api.tasks.getTask, {
taskId: args.taskId,
});
if (!task) throw new Error("Task not found");
// 3. Call external service (using Node.js SDK)
await sendgrid.send({
to: identity.email,
from: "noreply@example.com",
subject: "Task Reminder",
text: `Don't forget: ${task.text}`,
});
// 4. Update via mutation
await ctx.runMutation(api.tasks.markReminderSent, {
taskId: args.taskId,
});
return true;
},
});
Note: Keep queries and mutations in convex/tasks.ts (without "use node"), and actions that need Node.js in convex/taskActions.ts (with "use node").
For backend-only functions (called by scheduler, other functions):
import { internalMutation } from "./_generated/server";
export const processExpiredTasks = internalMutation({
args: {},
handler: async (ctx) => {
// No auth needed - only callable from backend
const now = Date.now();
const expired = await ctx.db
.query("tasks")
.withIndex("by_due_date", q => q.lt("dueDate", now))
.collect();
for (const task of expired) {
await ctx.db.patch(task._id, { status: "expired" });
}
},
});
args defined with validatorsreturns defined with validatorctx.auth.getUserIdentity()).filter() on queries)internal.* not api.*"use node" at top of file"use node": Only actions (no queries/mutations)