Skip to main content

Data Modeling

Document IDs

Every document in convex has a globally unique document ID that is automatically generated by the system.

const userId = db.insert("users", { name: "Michael Jordan" });

You can use this ID to always issue a very efficient point query within your functions using the get method:

const retrievedUser = await db.get(documentId);

Also, this same ID can be used to update that document in place:

db.patch(documentId, { name: "Steph Curry" });

You can access the ID of a document in the _id field:

const userId = user._id;

Convex generates an Id TypeScript type based on your schema that is parameterized over your table names:

import { Id } from "./_generated/dataModel";

const userId: Id<"users"> = user._id;
caution

Id is a class. To check if two Ids refer to the same document, use myId.equals(otherId).

Importantly, === won't work because two different instances of this class can refer to the same document.

References and relationships

In Convex, you can reference a document simply by embedding its Id in another document:

db.insert("books", {
title,
ownerId: user._id,
});

You can follow references with db.get:

const user = await db.get(book.ownerId);

And query for documents with a reference:

const myBooks = await db
.table("books")
.filter(q => q.eq(q.field("ownerId"), user._id))
.collect();

Using Ids as references can allow you to build a complex data model.

Trading off deeply nested documents vs. relationships

While it's useful that Convex supports nested objects and arrays, in general you should keep documents relatively small in size. In practice, we recommend limiting lists to no more than 5-10 elements and avoiding deeply nested documents.

Instead, leverage separate tables, documents, and references to structure your data. This will lead to better maintainability and performance as your project grows.