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

OperationDescriptionExample
ctx.db.get(id)Fetch a single document by IDawait ctx.db.get(args.id)
ctx.db.insert(table, doc)Insert a new documentawait ctx.db.insert("tasks", { ... })
ctx.db.patch(id, fields)Update specific fieldsawait ctx.db.patch(id, { title: "New" })
ctx.db.replace(id, doc)Replace the entire documentawait ctx.db.replace(id, newDoc)
ctx.db.delete(id)Remove a documentawait 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.