Queries & Mutations
Convex server functions come in two flavors: queries for reading data and mutations for writing data. Both run on the server with full access to the database.
Writing a query
Queries are read-only functions created with the query builder. They receive a context object
(ctx) that provides ctx.db for database access.
// convex/tasks.ts
import { query } from "./_generated/server";
import { v } from "convex/values";
export const listByProject = query({
args: { projectId: v.id("projects") },
handler: async (ctx, args) => {
return await ctx.db
.query("tasks")
.withIndex("by_project", (q) => q.eq("projectId", args.projectId))
.order("desc")
.collect();
},
});
Writing a mutation
Mutations can read and write data. Use ctx.db.insert, ctx.db.patch, ctx.db.replace, or
ctx.db.delete to modify documents.
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const create = mutation({
args: {
projectId: v.id("projects"),
title: v.string(),
priority: v.union(v.literal("low"), v.literal("medium"), v.literal("high")),
},
handler: async (ctx, args) => {
const taskId = await ctx.db.insert("tasks", {
projectId: args.projectId,
title: args.title,
priority: args.priority,
tags: [],
metadata: {},
});
return taskId;
},
});
Database operations
| Operation | Description | Example |
|---|---|---|
ctx.db.get(id) | Fetch a single document by ID | await ctx.db.get(args.id) |
ctx.db.insert(table, doc) | Insert a new document | await ctx.db.insert("tasks", { ... }) |
ctx.db.patch(id, fields) | Update specific fields | await ctx.db.patch(id, { title: "New" }) |
ctx.db.replace(id, doc) | Replace the entire document | await ctx.db.replace(id, newDoc) |
ctx.db.delete(id) | Remove a document | await ctx.db.delete(args.id) |
Error handling
Throw standard JavaScript errors inside handlers. Convex serializes the error message and delivers it to the client.
handler: async (ctx, args) => {
const project = await ctx.db.get(args.projectId);
if (!project) {
throw new Error("Project not found");
}
// continue...
},
Calling from the client
import { useQuery, useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";
const tasks = useQuery(api.tasks.listByProject, { projectId });
const createTask = useMutation(api.tasks.create);
Queries automatically re-run when data changes. Mutations return a promise that resolves once the write is confirmed.