Objects

Storing Data in a Moralis Server.

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 back-end will store them.

For example, let’s say you’re building an NFT game where the characters are monsters. A single Moralis.Object could contain:

strength: 1024, ownerName: "Aegon", canFly: true

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 object a LegendaryMonster.

We recommend that you NameYourClassesLikeThis and nameYourKeysLikeThis, just to keep your code looking pretty.

Moralis.Object.extend()

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 LegendaryMonster = Moralis.Object.extend("Monster");

// Create a new instance of that class.
const monster = new LegendaryMonster();

// Alternatively, you can use the typical Backbone syntax.
const LegendaryMonster = Moralis.Object.extend({
  className: "Monster",
});

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.

Save Objects

myObject.save()

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

const Monster = Moralis.Object.extend("Monster");
const monster = new Monster();

monster.set("strength", 1024);
monster.set("ownerName", "Aegon");
monster.set("canFly", true);

monster.save().then(
  (monster) => {
    // Execute any logic that should take place after the object is saved.
    alert("New object created with objectId: " + monster.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", strength: 1024, ownerName: "Aegon", canFly: true,
createdAt:"2011-06-10T18:33:42Z", updatedAt:"2011-06-10T18:33:42Z"

There are two things to note here:

  1. You didn’t have to configure or set up a new class called Monster before running this code.

  2. 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 been completed.

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

const Monster = Moralis.Object.extend("Monster");
const monster = new Monster();

monster
  .save({
    strength: 1024,
    ownerName: "Aegon",
    canFly: true,
  })
  .then(
    (monster) => {
      // The object was saved successfully.
    },
    (error) => {
      // The save failed.
      // error is a Moralis.Error with an error code and message.
    }
  );

By default, the classes you create will have no permissions set - meaning that anyone can write data into the class and read data from the class. Please see Security Docs about securing your classes and adding permissions.

By default the classes you create will have no permissions set meaning that anyone can write data into the class and read data from the class. Please see Security Docs about securing your classes and adding permissions.

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 monster’s profile that has an owner property pointing 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 Monster = Moralis.Object.extend("Monster");
const monster = new Monster();
monster.set("ownerAccount", ownerAccount); // Suppose `ownerAccount` has been created earlier.

monster.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 as it has been executed. For example:

const Monster = Moralis.Object.extend("Monster");
const monster = new Monster();
monster.set("species", "Dragon");

const context = { notifyMonsterGuild: true };
await monster.save(null, { context: context });

The context is then accessible in cloud code:

Moralis.Cloud.afterSave("Monster", async (req) => {
  const notifyMonsterGuild = req.context.notifyMonsterGuild;
  if (notifyMonsterGuild) {
    // Notify the guild about new monster.
  }
});

Retrieve Objects

myObject.get()

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 retrieve it using a Moralis.Query:

const Monster = Moralis.Object.extend("Monster");
const query = new Moralis.Query(Monster);

//get monster with id xWMyZ4YEGZ
query.get("xWMyZ4YEGZ").then(
  (monster) => {
    // 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 strength = monster.get("strength");
const ownerName = monster.get("ownerName");
const canFly = monster.get("canFly");

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

const { strength, ownerName, canFly } = 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 updatedAt = monster.updatedAt;
const objectId = monster.id;
const createdAt = monster.createdAt;
const acl = monster.getACL();

myObject.fetch()

If you need to refresh an object you already have with the latest data currently 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.
  }
);

myObject.isDataAvailable()

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

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

Update Objects

myObject.set()

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

// Create the object.
const Monster = Moralis.Object.extend("Monster");
const monster = new Monster();

monster.set("strength", 1024);
monster.set("energy", 1337);
monster.set("owner", "Aegon");
monster.set("canFly", false);
monster.set("skills", ["pwnage", "flying"]);

monster.save().then((monster) => {
  // Now let's update it with some new data. In this case, only canFly and energy
  // will get sent to the cloud. ownerName hasn't changed.
  monster.set("canFly", true);
  monster.set("energy", 1338);
  return monster.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.

myObject.increment()

COUNTERS

The above example contains a common use case. The strength field is a counter that we’ll need to continually update with the monster's latest energy. 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 automatically increment (or decrement) any number field. So, the same update can be rewritten as:

monster.increment("energy");
monster.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.

myObject.addUnique()

ARRAYS

To help with storing array data, there are three operations that can be used to automatically 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.

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

Note that it is not currently possible to automatically 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.

Destroy Objects

myObject.destroy()

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.
  }
);

myObject.unset()

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

// After this, the ownerName field will be empty
myObject.unset("ownerName");

// 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 the 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 kinds of relationships:

  1. One-to-one

  2. One-to-many

  3. Many-to-many.

One-to-One AND One-to-Many

One-to-one and one-to-many relationships are modelled 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, Moralis will store the referred-to object in just one place, to maintain consistency. You can also link objects using just their objectId's like so:

const post = new Post();
post.id = "1zEcyElZ80";

myComment.set("parent", post);

By default, when fetching an object, related Moralis.Object's 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

Many-to-many relationships are modelled using Moralis.Relation. This works similar to storing an array of Moralis.Object's 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 relation = myObject.relation()

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()

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

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

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

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

relation.add()

Example
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()

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

If you only want 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 Queries 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:

Example
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.File 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.

Tutorial

Legacy UI could be present in these videos, some things might be slightly different

Last updated