Skip to main content
Convex provides a reactive database that automatically keeps your application’s UI in sync with your backend data. When data changes in the database, all queries that depend on that data automatically re-run and update connected clients in real-time.

How reactivity works

The reactive database model in Convex is built on three core principles:
  1. Automatic dependency tracking - When you run a query, Convex tracks which documents and indexes your query reads
  2. Change detection - When a mutation modifies data, Convex identifies which queries are affected
  3. Automatic re-execution - Affected queries automatically re-run and push updates to subscribed clients
This means you write simple, declarative queries and Convex handles all the complexity of keeping data synchronized.

Reading from the database

The database reader interface (ctx.db) provides two primary entry points:

Fetching by ID

Use db.get() to fetch a single document by its ID:

Querying multiple documents

Use db.query() to build more complex queries:
Always prefer .withIndex() over .filter() for better performance. Indexes allow Convex to efficiently find matching documents, while filters must scan all documents.

Writing to the database

Mutations provide a database writer interface (ctx.db) with four write operations:

Insert

Add new documents to a table:
System fields (_id and _creationTime) are added automatically.

Patch

Shallow merge updates into an existing document:
Fields not specified remain unchanged. Set fields to undefined to remove them.

Replace

Completely replace a document (except system fields):

Delete

Remove a document from the database:

Transactional guarantees

All reads and writes within a single query or mutation are atomic and isolated:
  • Queries see a consistent snapshot of the database at a single point in time
  • Mutations execute all writes atomically - either all succeed or all fail
  • No partial states or race conditions - you never see inconsistent data
If any operation throws an error, all changes are automatically rolled back.

System tables

Convex provides read-only access to system tables through ctx.db.system:
System tables include:
  • _storage - File metadata for stored files
  • _scheduled_functions - State of scheduled functions

Query patterns

The database reader supports several patterns for consuming query results:
.collect() loads all matching documents into memory. Only use it when the result set is tightly bounded. For large or unbounded result sets, prefer .take(n), .first(), .unique(), or pagination.

Optimistic concurrency control

Convex uses optimistic concurrency control (OCC) for mutations. If a mutation reads data that was modified by another concurrent mutation, Convex automatically retries the mutation with fresh data. You don’t need to handle this explicitly - Convex manages retries transparently. Just write your mutation logic as if it runs alone:

Real-time updates

When you use queries in your client application with React hooks like useQuery, the results automatically update when underlying data changes:
Convex tracks dependencies and pushes updates efficiently, typically within 50-100ms of the data changing.

Next steps

  • Learn about Functions to understand queries, mutations, and actions
  • Define Schemas to validate your data and get TypeScript types
  • Explore Real-time sync to understand the synchronization protocol