Server API

Learn how to work with Convex backend functions and database.

Convex Functions

Convex provides three types of functions for your backend logic:

  • Queries - Read data from the database
  • Mutations - Write data to the database
  • Actions - Perform external API calls or non-deterministic operations

Writing a Query

import { query } from "./_generated/server";
import { v } from "convex/values";

export const getUser = query({
  args: { userId: v.id("users") },
  handler: async (ctx, args) => {
    return await ctx.db.get(args.userId);
  },
});

Writing a Mutation

import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const createTask = mutation({
  args: { text: v.string() },
  handler: async (ctx, args) => {
    const taskId = await ctx.db.insert("tasks", {
      text: args.text,
      completed: false,
    });
    return taskId;
  },
});

Authentication in Functions

Use the getAuthUserId helper to get the authenticated user:

import { getAuthUserId } from "@convex-dev/auth/server";

export const myProtectedFunction = mutation({
  handler: async (ctx) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) {
      throw new Error("Not authenticated");
    }
    // Function logic here
  },
});

Database Schema

Define your database schema in convex/schema.ts:

import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  tasks: defineTable({
    text: v.string(),
    completed: v.boolean(),
    userId: v.id("users"),
  }).index("by_user", ["userId"]),
});

Best Practices

  • Always validate function arguments using Convex validators
  • Use indexes for efficient querying
  • Implement proper authentication checks in protected functions
  • Keep functions small and focused on a single responsibility
  • Use transactions for atomic operations