Writing Ents to the Database
Just like for reading ents from the database, for writing Convex Ents
provide a ctx.table
method which replaces the built-in ctx.db
object in
Convex mutations (opens in a new tab).
Security
The same added level of security applies to the writing ents as it does to reading them.
Inserting a new ent
You can insert new ents into the database with the insert
method chained to
the result of calling ctx.table
:
const taskId = await ctx.table("tasks").insert({ text: "Win at life" });
You can retrieve the just created ent with the get
method:
const task = await ctx.table("tasks").insert({ text: "Win at life" }).get();
This is equivalent to the built-in:
const taskId = await ctx.db.insert("tasks", { text: "Win at life" });
const task = (await ctx.db.get(taskId))!;
Inserting many new ents
const taskIds = await ctx
.table("tasks")
.insertMany({ text: "Buy socks" }, { text: "Buy socks" });
Updating existing ents
To update an existing ent, call the patch
or replace
method on a
lazy Promise
of an ent, or on an already retrieved ent:
await ctx.table("tasks").getX(taskId).patch({ text: "Changed text" });
await ctx.table("tasks").getX(taskId).replace({ text: "Changed text" });
const task = await ctx.table("tasks").getX(taskId);
await task.patch({ text: "Changed text" });
await task.replace({ text: "Changed text" });
See the
docs for the built-in patch
and replace
methods (opens in a new tab)
for the difference between them.
Deleting ents
To delete an ent, call the delete
method on a lazy Promise
of an
ent, or on an already retrieved ent:
await ctx.table("tasks").getX(taskId).delete();
const task = await ctx.table("tasks").getX(taskId);
await task.delete();
Cascading deletes
See the Cascading Deletes page for how to configure how deleting an ent affects its edges and other ents connected to it.
Creating edges
Edges can be created together with ents using the insert
and insertMany
methods, or they can be created for two existing ents using the replace
and
patch
methods.
Creating 1:1 and 1:many edges
A 1:1 or 1:many edge can be created by specifying the ID of the other ent on the ent which stores the edge, either when inserting:
// First we need a user, which can have an optional profile edge
const userId = await ctx.table("users").insert({ name: "Alice" });
// Now we can create a profile with the 1:1 edge to the user
const profileId = await ctx
.table("profiles")
.insert({ bio: "In Wonderland", userId });
or when updating:
const profileId = await ctx.table("profiles").getX(profileId).patch({ userId });
This is equivalent to the built-in:
const posts = await ctx.db.patch(profileId, { userId });
with the addition of checking that profileId
belongs to "profiles"
.
Creating many:many edges
Many:many edges can be created by listing the IDs of the other ents when inserting ents on either side of the edge:
// First we need a tag, which can have many:many edge to messages
const tagId = await ctx.table("tags").insert({ name: "Blue" });
// Now we can create a message with a many:many edge to the tag
const messageId = await ctx
.table("messages")
.insert({ text: "Hello world", tags: [tagId] });
But we could have equally created a message first, and then created a tag with a list of message IDs.
The replace
method also expects a list of IDs:
await ctx
.table("messages")
.getX(messageId)
.replace({ text: "Changed message", tags: [tagID, otherTagID] });
Because replace
essentially behaves like a delete
+ insert
but preserves
the _id
and _creationTime
fields of the replaced ent, any edges which are
not listed will be deleted.
The patch
method on the other hand expects a description of the changes that
should be made, a list of IDs to add
and remove
edges for:
const message = await ctx.table("messages").getX(messageId);
await message.patch({ tags: { add: [tagID] } });
await message.patch({
tags: { add: [tagID, otherTagID], remove: [tagToDeleteID] },
});
Any edges in the add
list that didn't exist are created, and any edges in the
remove
list that did exist are deleted. Edges to ents with ID not listed in
either list are not affected by patch
.
Updating ents connected by edges
The patch
, replace
and delete
methods can be chained after edge
calls to
update the ent on the other end of an edge:
await ctx
.table("users")
.getX(userId)
.edgeX("profile")
.patch({ bio: "I'm the first user" });
This is equivalent to the built-in:
const profile = await ctx.db
.query("profiles")
.withIndex("userId", (q) => q.eq("userId", userId))
.unique();
if (profile === null) {
throw new Error(
`Edge "profile" does not exist for document wit ID "${userId}"`,
);
}
await ctx.db.patch(profile._id, { bio: "I'm the first user" });
Limitation: edge
called on a loaded ent
The following code does not typecheck currently (opens in a new tab):
const user = await ctx.table("users").getX(userId);
await user.edgeX("profile").patch({ bio: "I'm the first user" });
You can either disable typechecking via // @ts-expect-error
or preferably
start with ctx.table
:
const user = ... // loaded or passed via `map`
await ctx
.table("users")
.getX(user._id).edgeX("profile").patch({ bio: "I'm the first user" });