Inserting and updating in server-side code
Apostrophe provides module methods for inserting and updating content documents in the database. These methods should be the primary tools for developers doing server-side data operations since they emit events that the CMS uses to keep all database collections updated and in sync. We will look at methods for both pieces and pages, which are similar but have some important differences.
INFO
Remember that the Apostrophe REST APIs for pieces and pages are usually the best option when triggering content updates from the browser. Those API endpoints take advantage of the methods below while adding logic important to use in browsers.
Updating content documents
When we have an existing piece document that to update, we use the update()
method on the related piece type module. The update
method takes the following arguments:
Argument | Required | Description |
---|---|---|
req | TRUE | The associated request object. Using a provided req object is important for maintaining user role permissions. |
piece | TRUE | The document object that will replace the existing database document. |
options | FALSE | An options object, currently only used for internal draft state management. |
For example, if we had a dog adoption website that used an external API, we may want to update dog
pieces periodically with adoption status. This may be using a command line task that is run regularly by a cron job.
What is happening here?
async updateDogStatus(req, dogId, status) {
const dogDocument = await self.find(req, {
dogId
}).toObject();
...
}
Our method received the active request object, the API's identifier for a dog, and the dog's availability status (probably a string value). We then request the full database document. See the database querying guide for more on this.
if (!dogDocument) {
return null;
};
If the identified dog isn't in our database yet, we would return a null
value and the update task's function could insert the dog instead.
// We update the `status` property.
dogDocument.status = status;
const updatedDraft = await self.update(req, dogDocument);
return updatedDraft;
We update the document property that tracks the dog's adoption status on the data object and use self.update
to replace the previous document state with our update (with await
as it is asynchronous). We finally return the result, which will be the updated document object.
Note that using the provided req
object like this works only if the req
object is from a user with at least "contributor" permissions for the dog
piece type. If we wanted to bypass that permission check, or if we ever wanted to allow anonymous site visitors to insert or update content, we would pass { permissions: false }
as a third options argument to self.update()
. That obviously raises security issues we would need to consider carefully.
INFO
All content documents have multiple versions, including "draft" and "published" versions. The update()
methods only updates the "draft" copy, allowing editors to still review before publishing. If we did want to publish here as well, we would want to run the publishing method:
await self.publish(req, updatedDraft);
What if we are updating a document from a different doc type module?
In the example above, self
refers to the dog
piece type module since that is where the method is registered. If we wanted to run the update
method from a separate module we would replace self
with a specific reference to the dog
module: self.apos.modules.dog
.
Updating page documents
Updating pages works the same way as pieces with the same arguments to the update
method. There are a few things to keep in mind when working on pages, however.
We typically call the page update
method from the main page module: self.apos.page.update()
. Since pages can change their type
property (unlike pieces) they share a single update
method from the @apostrophecms/page
module. self.update()
on individual page type modules is simply a wrapper around that method.
update()
is not the way to move pages within the page tree. The @apostrophecms/page
module has a dedicated move()
method for that purpose. The REST API information about page tree placement has additional information.
Inserting a new piece
Inserting a new piece works very similarly to updating an existing one. The self.insert
method takes the same arguments: self.insert(req, piece, options)
. The big difference, of course, is that there is no existing document to find, update, and resubmit.
Instead, use the self.newInstance()
method to get a fresh document object of the proper document type. That method uses the doc type's field schema to generate the essential document properties with any default values. We can then add any initial data to that essentially blank document object.
Inserting pages
As mentioned above, pages have a position within the page tree. The insert
method for pages therefore requires additional arguments to place a new page in that hierarchy properly. Also as described above we normally call the insert method directly from the @apostrophecms/page
module, aliased in server-side code as self.apos.page
.
The arguments for the self.apos.page.insert()
method are:
Argument | Required | Description |
---|---|---|
req | TRUE | The associated request object. Using a provided req object is important for maintaining user role permissions. |
_targetId | TRUE | The _id of an existing page to use as a target when inserting the new page. _home and _archive are optional conveniences for the home page and archived section, respectively. |
_position | TRUE | A numeric value will represent the zero-based child index under the _targetId page. before , after , firstChild , or lastChild values set the position within the page tree for the new page in relation to the target page (see _targetId ). before and after insert the new page as a sibling of the target. firstChild and lastChild insert the new page as a child of the target. |
page | TRUE | The page document object. |
options | FALSE | An options object, primarily used for internal draft state management. |
As with pieces, this process will normally begin by generating an empty page document with the newInstance()
method, which should be done using a specific page type module.
INFO
With very few exceptions, the _id
property is an automatically generated, randomized, and (always) unique property. It is also special in that it can never change for a given document.
Inserting and updating relationships
As we have seen, relationships can be accessed as array properties after we locate documents with the find
method.
By the same principle, relationships can be inserted or updated as array properties. If our dog
piece type seen above has an _owner
relationship with person
pieces, we can prepopulate it like this:
// assumes the alias: 'person' option has been set for the person piece type
const owner = await self.apos.person.find(req, { title: 'Frank' });
// Before calling insert() in the example above
newDog._owner = [ owner ];
const insertResult = await self.insert(req, newDog);
We can do the same thing with self.update
.
Note that inserting or updating a relationship in this way cannot insert or update related pieces that do not already exist. This technique only updates the relationship.
Inserting and updating relationship subfields
Some relationships have subfields of their own describing the relationship itself. These subfields can be set in the same way they are accessed: by populating the _fields
property of each related document. Here is an example:
// For this example to work, the _owner relationship field of the "dog" piece type
// must have a "favorite" subfield of type boolean
const owner = await self.apos.person.find(req, { title: 'Frank' });
owner._fields = {
favorite: true
};
newDog._owner = [ owner ];
const insertResult = await self.insert(req, newDog);
This works the same way whether we are inserting or updating.