BuildBot

Data with Convex

Schema design & indexes

Lesson 3 of 7

What you'll learn

  • Define a typed table with defineTable and validators
  • Understand why Convex requires explicit indexes
  • Name indexes after the fields they cover

Convex is a reactive database where your schema is TypeScript. You declare tables with validators, and Convex enforces them at write time:

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

export default defineSchema({
  progress: defineTable({
    clerkUserId: v.string(),
    lessonId: v.string(),
    courseSlug: v.string(),
    completedAt: v.number(),
  })
    .index("by_user", ["clerkUserId"])
    .index("by_user_and_lesson", ["clerkUserId", "lessonId"]),
});

Why indexes are mandatory

Most databases have a query planner that guesses how to run your query. Convex doesn't — you tell it exactly which index to use with .withIndex(). This trades a little upfront thought for predictable, fast queries that never silently degrade as your data grows.

A few rules from the Convex docs worth memorizing:

  • Up to 16 fields per index, 32 indexes per table.
  • _creationTime is appended to every index automatically.
  • Avoid .collect() on large/unbounded sets — use an index or pagination.

Naming convention

Name an index for the fields it contains: by_user, by_user_and_lesson, by_course. When you read the query later, the index name tells you the access pattern at a glance.

Picking the right index

An index on ["clerkUserId", "lessonId"] can answer "all progress for this user" and "this user's row for this exact lesson" — because index fields are matched left-to-right. The exercise models that prefix-matching.

How a compound index matches

The index is sorted by [userId, lessonId]. Run it to see which queries the index can serve efficiently and which can't.

Loading editor…
Knowledge check

Why does Convex require you to declare an index for each access pattern instead of choosing one for you?

With a schema in place, the magic begins: queries that update themselves.

Saved on this device. Sign in to sync your progress everywhere.