Objects

Moralis.Object

Storing data on Moralis is built around Moralis.Object. Each Moralis.Object contains key-value pairs of JSON-compatible data. This data is schemaless, which means that you don’t need to specify ahead of time what keys exist on each Moralis.Object. You simply set whatever key-value pairs you want, and our backend will store it.

For example, let’s say you’re tracking high scores for a game. A single Moralis.Object could contain:

score: 1337, playerName: "Sean Plott", cheatMode: false

Keys must be alphanumeric strings. Values can be strings, numbers, booleans, or even arrays and dictionaries - anything that can be JSON-encoded.

Each Moralis.Object is an instance of a specific subclass with a class name that you can use to distinguish different sorts of data. For example, we could call the high score object a GameScore. We recommend that you NameYourClassesLikeThis and nameYourKeysLikeThis, just to keep your code looking pretty.

To create a new subclass, use the Moralis.Object.extend method. Any Moralis.Query will return instances of the new class for any Moralis.Object with the same classname. If you’re familiar with Backbone.Model, then you already know how to use Moralis.Object. It’s designed to be created and modified in the same ways.

// Simple syntax to create a new subclass of Moralis.Object.
const GameScore = Moralis.Object.extend("GameScore");
// Create a new instance of that class.
const gameScore = new GameScore();
// Alternatively, you can use the typical Backbone syntax.
const Achievement = Moralis.Object.extend({
className: "Achievement"
});

You can add additional methods and properties to your subclasses of Moralis.Object.

// A complex subclass of Moralis.Object
const Monster = Moralis.Object.extend("Monster", {
// Instance methods
hasSuperHumanStrength: function () {
return this.get("strength") > 18;
},
// Instance properties go in an initialize method
initialize: function (attrs, options) {
this.sound = "Rawr"
}
}, {
// Class methods
spawn: function(strength) {
const monster = new Monster();
monster.set("strength", strength);
return monster;
}
});
const monster = Monster.spawn(200);
alert(monster.get('strength')); // Displays 200.
alert(monster.sound); // Displays Rawr.

To create a single instance of any Moralis Object class, you can also use the Moralis.Object constructor directly. new Moralis.Object(className) will create a single Moralis Object with that class name.

If you’re already using ES6 in your codebase. You can subclass Moralis.Object with the extends keyword:

class Monster extends Moralis.Object {
constructor() {
// Pass the ClassName to the Moralis.Object constructor
super('Monster');
// All other initialization
this.sound = 'Rawr';
}
hasSuperHumanStrength() {
return this.get('strength') > 18;
}
static spawn(strength) {
const monster = new Monster();
monster.set('strength', strength);
return monster;
}
}

However, when using extends, the SDK is not automatically aware of your subclass. If you want objects returned from queries to use your subclass of Moralis.Object, you will need to register the subclass, similar to what we do on other platforms.

// After specifying the Monster subclass...
Moralis.Object.registerSubclass('Monster', Monster);

Similarly, you can use extends with Moralis.User

class CustomUser extends Moralis.User {
constructor(attributes) {
super(attributes);
}
doSomething() {
return 5;
}
}
Moralis.Object.registerSubclass('_User', CustomUser);

In addition to queries, logIn and signUp will return the subclass CustomUser.

const customUser = new CustomUser({ foo: 'bar' });
customUser.setUsername('username');
customUser.setPassword('password');
customUser.signUp().then((user) => {
// user is an instance of CustomUser
user.doSomething(); // return 5
user.get('foo'); // return 'bar'
});

CustomUser.logIn and CustomUser.signUp will return the subclass CustomUser

Saving Objects

Let’s say you want to save the GameScore described above to the Moralis Cloud. The interface is similar to a Backbone.Model, including the save method:

const GameScore = Moralis.Object.extend("GameScore");
const gameScore = new GameScore();
gameScore.set("score", 1337);
gameScore.set("playerName", "Sean Plott");
gameScore.set("cheatMode", false);
gameScore.save()
.then((gameScore) => {
// Execute any logic that should take place after the object is saved.
alert('New object created with objectId: ' + gameScore.id);
}, (error) => {
// Execute any logic that should take place if the save fails.
// error is a Moralis.Error with an error code and message.
alert('Failed to create new object, with error code: ' + error.message);
});

After this code runs, you will probably be wondering if anything really happened. To make sure the data was saved, you can look at the Data Browser in your Moralis Dashboard. You should see something like this:

objectId: "xWMyZ4YEGZ", score: 1337, playerName: "Sean Plott", cheatMode: false,
createdAt:"2011-06-10T18:33:42Z", updatedAt:"2011-06-10T18:33:42Z"

There are two things to note here. You didn’t have to configure or set up a new Class called GameScore before running this code. Your Moralis app lazily creates this Class for you when it first encounters it.

There are also a few fields you don’t need to specify that are provided as a convenience. objectId is a unique identifier for each saved object. createdAt and updatedAt represent the time that each object was created and last modified in the cloud. Each of these fields is filled in by Moralis, so they don’t exist on a Moralis.Object until a save operation has completed.

If you prefer, you can set attributes directly in your call to save instead.

const GameScore = Moralis.Object.extend("GameScore");
const gameScore = new GameScore();
gameScore.save({
score: 1337,
playerName: "Sean Plott",
cheatMode: false
})
.then((gameScore) => {
// The object was saved successfully.
}, (error) => {
// The save failed.
// error is a Moralis.Error with an error code and message.
});

SAVING NESTED OBJECTS

You may add a Moralis.Object as the value of a property in another Moralis.Object. By default, when you call save() on the parent object, all nested objects will be created and/or saved as well in a batch operation. This feature makes it really easy to manage relational data as you don’t have to take care of creating the objects in any specific order.

const Child = Moralis.Object.extend("Child");
const child = new Child();
const Parent = Moralis.Object.extend("Parent");
const parent = new Parent();
parent.save({ child: child });
// Automatically the object Child is created on the server
// just before saving the Parent

In some scenarios, you may want to prevent this default chain save. For example, when saving a team member’s profile that points to an account owned by another user to which you don’t have write access. In this case, setting the option cascadeSave to false may be useful:

const TeamMember = Moralis.Object.extend("TeamMember");
const teamMember = new TeamMember();
teamMember.set('ownerAccount', ownerAccount); // Suppose `ownerAccount` has been created earlier.
teamMember.save(null, { cascadeSave: false });
// Will save `teamMember` wihout attempting to save or modify `ownerAccount`

CLOUD CODE CONTEXT

You may pass a context dictionary that is accessible in Cloud Code beforeSave and afterSave triggers for that Moralis.Object. This is useful if you want to condition certain operations in Cloud Code triggers on ephemeral information that should not be saved with the Moralis.Object in the database. The context is ephemeral in the sense that it vanishes after the Cloud Code triggers for that particular Moralis.Object have executed. For example:

const TeamMember = Moralis.Object.extend("TeamMember");
const teamMember = new TeamMember();
teamMember.set("team", "A");
const context = { notifyTeam: false };
await teamMember.save(null, { context: context });

The context is then accessible in Cloud Code:

Moralis.Cloud.afterSave("TeamMember", async (req) => {
const notifyTeam = req.context.notifyTeam;
if (notifyTeam) {
// Notify team about new member.
}
});

Retrieving Objects

Saving data to the cloud is fun, but it’s even more fun to get that data out again. If the Moralis.Object has been uploaded to the server, you can use the objectId to get it using a Moralis.Query:

const GameScore = Moralis.Object.extend("GameScore");
const query = new Moralis.Query(GameScore);
query.get("xWMyZ4YEGZ")
.then((gameScore) => {
// The object was retrieved successfully.
}, (error) => {
// The object was not retrieved successfully.
// error is a Moralis.Error with an error code and message.
});

To get the values out of the Moralis.Object, use the get method.

const score = gameScore.get("score");
const playerName = gameScore.get("playerName");
const cheatMode = gameScore.get("cheatMode");

Alternatively, the attributes property of the Moralis.Object can be treated as a Javascript object, and even destructured.

const { score, playerName, cheatMode } = result.attributes;

The four special reserved values are provided as properties and cannot be retrieved using the ‘get’ method nor modified with the ‘set’ method:

const objectId = gameScore.id;
const updatedAt = gameScore.updatedAt;
const createdAt = gameScore.createdAt;
const acl = gameScore.getACL();

If you need to refresh an object you already have with the latest data that is in the Moralis Cloud, you can call the fetch method like so:

myObject.fetch().then((myObject) => {
// The object was refreshed successfully.
}, (error) => {
// The object was not refreshed successfully.
// error is a Moralis.Error with an error code and message.
});

If you need to check if an object has been fetched, you can call the isDataAvailable() method:

if (!myObject.isDataAvailable()) {
await myObject.fetch();
}

Updating Objects

Updating an object is simple. Just set some new data on it and call the save method. For example:

// Create the object.
const GameScore = Moralis.Object.extend("GameScore");
const gameScore = new GameScore();
gameScore.set("score", 1337);
gameScore.set("playerName", "Sean Plott");
gameScore.set("cheatMode", false);
gameScore.set("skills", ["pwnage", "flying"]);
gameScore.save().then((gameScore) => {
// Now let's update it with some new data. In this case, only cheatMode and score
// will get sent to the cloud. playerName hasn't changed.
gameScore.set("cheatMode", true);
gameScore.set("score", 1338);
return gameScore.save();
});

Moralis automatically figures out which data has changed so only “dirty” fields will be sent to the Moralis Cloud. You don’t need to worry about squashing data that you didn’t intend to update.

COUNTERS

The above example contains a common use case. The “score” field is a counter that we’ll need to continually update with the player’s latest score. Using the above method works but it’s cumbersome and can lead to problems if you have multiple clients trying to update the same counter.

To help with storing counter-type data, Moralis provides methods that atomically increment (or decrement) any number field. So, the same update can be rewritten as:

gameScore.increment("score");
gameScore.save();

You can also increment by any amount by passing in a second argument to increment. When no amount is specified, 1 is used by default.

ARRAYS

To help with storing array data, there are three operations that can be used to atomically change an array associated with a given key:

  • add append the given object to the end of an array field.

  • addUnique add the given object only if it isn’t already contained in an array field. The position of the insert is not guaranteed.

  • remove remove all instances of the given object from an array field.

gameScore.addUnique("skills", "flying");
gameScore.addUnique("skills", "kungfu");
gameScore.save();

Note that it is not currently possible to atomically add and remove items from an array in the same save. You will have to call save in between every different kind of array operation.

Destroying Objects

To delete an object from the cloud:

myObject.destroy().then((myObject) => {
// The object was deleted from the Moralis Cloud.
}, (error) => {
// The delete failed.
// error is a Moralis.Error with an error code and message.
});

You can delete a single field from an object with the unset method:

// After this, the playerName field will be empty
myObject.unset("playerName");
// Saves the field deletion to the Moralis Cloud.
// If the object's field is an array, call save() after every unset() operation.
myObject.save();

Please note that use of object.set(null) to remove a field from an object is not recommended and will result in unexpected functionality.

Relational Data

Objects may have relationships with other objects. For example, in a blogging application, a Post object may have many Comment objects. Moralis supports all kind of relationships, including one-to-one, one-to-many, and many-to-many.

ONE-TO-ONE AND ONE-TO-MANY RELATIONSHIPS

One-to-one and one-to-many relationships are modeled by saving a Moralis.Object as a value in the other object. For example, each Comment in a blogging app might correspond to one Post.

To create a new Post with a single Comment, you could write:

// Declare the types.
const Post = Moralis.Object.extend("Post");
const Comment = Moralis.Object.extend("Comment");
// Create the post
const myPost = new Post();
myPost.set("title", "I'm Hungry");
myPost.set("content", "Where should we go for lunch?");
// Create the comment
const myComment = new Comment();
myComment.set("content", "Let's do Sushirrito.");
// Add the post as a value in the comment
myComment.set("parent", myPost);
// This will save both myPost and myComment
myComment.save();

Internally, the Moralis will store the referred-to object in just one place, to maintain consistency. You can also link objects using just their objectIds like so:

const post = new Post();
post.id = "1zEcyElZ80";
myComment.set("parent", post);

By default, when fetching an object, related Moralis.Objects are not fetched. These objects’ values cannot be retrieved until they have been fetched like so:

const post = fetchedComment.get("parent");
await post.fetch();
const title = post.get("title");

MANY-TO-MANY RELATIONSHIPS

Many-to-many relationships are modeled using Moralis.Relation. This works similar to storing an array of Moralis.Objects in a key, except that you don’t need to fetch all of the objects in a relation at once. In addition, this allows Moralis.Relation to scale to many more objects than the array of Moralis.Object approach. For example, a User may have many Posts that she might like. In this case, you can store the set of Posts that a User likes using relation. In order to add a Post to the “likes” list of the User, you can do:

const user = Moralis.User.current();
const relation = user.relation("likes");
relation.add(post);
user.save();

You can remove a post from a Moralis.Relation:

relation.remove(post);
user.save();

You can call add and remove multiple times before calling save:

relation.remove(post1);
relation.remove(post2);
user.save();

You can also pass in an array of Moralis.Object to add and remove:

relation.add([post1, post2, post3]);
user.save();

By default, the list of objects in this relation are not downloaded. You can get a list of the posts that a user likes by using the Moralis.Query returned by query. The code looks like:

relation.query().find({
success: function(list) {
// list contains the posts that the current user likes.
}
});

If you want only a subset of the Posts, you can add extra constraints to the Moralis.Query returned by query like this:

const query = relation.query();
query.equalTo("title", "I'm Hungry");
query.find({
success:function(list) {
// list contains post liked by the current user which have the title "I'm Hungry".
}
});

For more details on Moralis.Query, please look at the query portion of this guide. A Moralis.Relation behaves similar to an array of Moralis.Object for querying purposes, so any query you can do on an array of objects, you can do on a Moralis.Relation.

Data Types

So far we’ve used values with type String, Number, and Moralis.Object. Moralis also supports Dates and null. You can nest JSON Objects and JSON Arrays to store more structured data within a single Moralis.Object. Overall, the following types are allowed for each field in your object:

  • String => String

  • Number => Number

  • Bool => bool

  • Array => JSON Array

  • Object => JSON Object

  • Date => Date

  • File => Moralis.File

  • Pointer => other Moralis.Object

  • Relation => Moralis.Relation

  • Null => null

Some examples:

const number = 42;
const bool = false;
const string = "the number is " + number;
const date = new Date();
const array = [string, number];
const object = { number: number, string: string };
const pointer = MyClassName.createWithoutData(objectId);
const BigObject = Moralis.Object.extend("BigObject");
const bigObject = new BigObject();
bigObject.set("myNumber", number);
bigObject.set("myBool", bool);
bigObject.set("myString", string);
bigObject.set("myDate", date);
bigObject.set("myArray", array);
bigObject.set("myObject", object);
bigObject.set("anyKey", null); // this value can only be saved to an existing key
bigObject.set("myPointerKey", pointer); // shows up as Pointer <MyClassName> in the Data Browser
bigObject.save();

We do not recommend storing large pieces of binary data like images or documents on Moralis.Object. We recommend you use Moralis.Files to store images, documents, and other types of files. You can do so by instantiating a Moralis.File object and setting it on a field. See Files for more details.

For more information about how Moralis handles data, check out our documentation on Data.