Document IDs
Example: Relational Data Modeling
Every document in convex has a globally unique string document ID that is automatically generated by the system.
const userId = await ctx.db.insert("users", { name: "Michael Jordan" });
You can use this ID to efficiently read a single document using the get
method:
const retrievedUser = await ctx.db.get(userId);
You can access the ID of a document in the
_id
field:
const userId = retrievedUser._id;
Also, this same ID can be used to update that document in place:
await ctx.db.patch(userId, { name: "Steph Curry" });
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;
IDs are strings at runtime, but the Id
type
can be used to distinguish IDs from other strings at compile time.
References and relationships
In Convex, you can reference a document simply by embedding its Id
in another
document:
await ctx.db.insert("books", {
title,
ownerId: user._id,
});
You can follow references with ctx.db.get
:
const user = await ctx.db.get(book.ownerId);
And query for documents with a reference:
const myBooks = await ctx.db
.query("books")
.filter((q) => q.eq(q.field("ownerId"), user._id))
.collect();
Using Id
s 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, you should keep documents relatively small in size. In practice, we recommend limiting Arrays to no more than 5-10 elements and avoiding deeply nested Objects.
Instead, leverage separate tables, documents, and references to structure your data. This will lead to better maintainability and performance as your project grows.
Serializing IDs
IDs are strings, which can be easily inserted into URLs or stored outside of Convex.
You can pass an ID string from an external source (like a URL) into a Convex
function and get the corresponding object. If you're using TypeScript on the
client you can cast a string to the Id
type:
import { useQuery } from "convex/react";
import { Id } from "../convex/_generated/dataModel";
import { api } from "../convex/_generated/api";
export function App() {
const id = localStorage.getItem("myIDStorage");
const task = useQuery(api.tasks.getTask, { taskId: id as Id<"tasks"> });
// ...
}
Since this ID is coming from an external source, use an argument validator or
ctx.db.normalizeId
to confirm that the ID belongs to the expected table before returning the
object.
import { query } from "./_generated/server";
import { v } from "convex/values";
export const getTask = query({
args: {
taskId: v.id("tasks"),
},
handler: async (ctx, args) => {
const task = await ctx.db.get(args.taskId);
// ...
},
});