JavaScript Guide

Make amazing applications for the web, real-time included.

Before you begin

The Basics

Setting up a new project

  • Open your system's terminal and create a new directory for your app. You shoud read our Command Line Guide if you don't know how to do this.
  • Move to your new directory and run appstax init to set up everything needed to work with your app.

You will be asked to choose a template to work from:

  • The basic template will create the minimum set of files neccessary for deployment in a "public" subdirectory.
  • The angular template will create a setup based on the excellent Angular JS framework, using npm, bower and gulp for building and includes Bootstrap for layout and UI components. It includes a README.md file with more detailed instructions. Use this template if you are already familiar with these technologies.

Now that everything has been properly set up you can run appstax serve and open http://localhost:9000/ to se your app running.

Including in an existing project

If you want to use Appstax in an existing project, just run appstax init from your project directory and then select your deployable subdirectory when asked. The appstax.js file will be downloaded to the root of that directory, and you can move it anywhere you want it. You can include it with a plain <script> tag and access the appstax global variable, or use it as an AMD or CommonJS module in your build setup.

There are also packages available for npm and bower, just install "appstax" as a dependency in your package.json or bower.json. If you work with static dependencies you can manually download the latest release and copy appstax.js or appstax.min.js into your project.

Initialize Appstax

Before making any API calls, you need to initialize the framework. Add this code to your app, replacing YourAppKey with the AppKey you received when signing up.:

appstax.init("YourAppKey");

When using the appstax init command, your app key will be automatically inserted when you choose a template. In the basic project template you will find this line in public/app.js. In the angular template it's in app/modules/app.js.

Working with objects

Create & save objects

Say we want to crate a public message board. To send a message we just write the following code:

var message = appstax.object("messages");
message.text = "Hello World!";
message.save();

In line 1, we create an object that will be stored to the messages collection. Line 2 sets the `text property of our new object. And in line 3 we save the object to the Appstax server.

Before you can create objects from code you must define your collection using the Databrowser web interface. So, to start saving message objects to Appstax you must first log on to the Databrowser and create a collection named "messages" and add a column "text" with type string.

Remember that collection names are case sensitive, so a collection named Messages won't match appstax.object("messages")

Asynchronous communication & promises

The message.save() call in the previous example kicks off an asynchronous HTTP call to the server and immediately returns control to your next line of code in order to avoid blocking your UI.

If you want to know when the save has completed, you can use the returned promise with a call to .then():

message.save()
       .then(function() {
           // ... object has been saved
       })
       .fail(function(error) {
           // Request failed. error.message contains explanation of what went wrong
       });

Load all objects in a collection

If you want to load all the objects of a collection you just:

appstax.findAll("messages")
       .then(function(objects) {
           // ... objects is an array of all the messages
       })
       .fail(function(error) {
           // Request failed. error.message contains explanation of what went wrong
       });

Using the returned objects

The returned array of objects is not further managed by appstax, and you can use it in any way you like. You could loop through and access its properties:

appstax.findAll("messages")
       .then(function(objects) {
           for(var i = 0; i < objects.length; i++) {
               var text  = objects[i].text;
               // ... and do something interesting with the text
           }
       })
       .fail(function(error) {
           // Request failed. error.message contains explanation of what went wrong
       });

... or you can use the objects with a templating engine::

// This example uses mustache.js for templating and assumes 
// there is a <div id="output"></div> in the html page.

appstax.findAll("messages")
       .then(function(objects) {
           var output = document.getElementById("output");
           var template = "<ul>{{#messages}}<li>{{text}})</li>{{/messages}}</ul>";
           output.innerHTML = Mustache.render(template, {messages:objects});
       })
       .fail(function(error) {
           // Request failed. error.message contains explanation of what went wrong
       });

Sorting and paging

You can request that the objects be returned sorted by a certain property, and the sort order (ascending or descending).

Also, if the collection contains a large number of objects, and you do not need to retrieve all of them at once, you can retrieve a certain page of objects. Each page contains pageSize of objects, by default 200.

Paging is applied after sorting.

In the following example, we ask for all objects in the messages collection, and we want them sorted by the sysUpdated property. By prefixing with a - we indicate that we want to sort by sysUpdated descending, ie last update first. (The default, no - prefix indicates ascending order.)

We also indicate that we want to page the data, so we only retrieve the third page and that the page contain 300 objects ({page: 2, pageSize:300})

appstax.findAll("messages", {page: 2, pageSize: 300, order: "-sysUpdated"})
       .then(function(objects) {
       })
       .fail(function(error) {
       });

Object id

The id of an object is accessible in a read-only property object.id:

var object = appstax.object("messages");
console.log(object.id); // object.id is null before it has been saved

object.save()
      .then(function(object) {
          console.log(object.id); // object.id matches the sysObjectId
                                  // column in the databrowser.
      })
      .fail(function(error) {
          // Request failed. error.message contains explanation of what went wrong
      });

Load a single object

For loading a single object the following lines will do:

appstax.find("messages", "1234")
       .then(function(object) {
           // ... object with id "1234"
       })
       .fail(function(error) {
           // Request failed. error.message contains explanation of what went wrong
       });

Remove an object

Just call remove and it will be deleted from your datastore:

message.remove()
       .then(function() {
           // ... object has been removed
       })
       .fail(function(error) {
           // Request failed. error.message contains explanation of what went wrong
       });

Refresh an object

If you already have an object but the data may have changed on the server, you can refresh it:

message.refresh()
       .then(function() {
           // ... the message object has been updated
       })
       .fail(function(error) {
           // Request failed. error.message contains explanation of what went wrong
       });

This works the same way for find(), findAll() and remove() too.

Querying Objects

There are several ways to find a selection of objects in a collection. We provide a generic find() that can be used for any request, and more specific methods for the most common use cases.

Matching property values

You can specify the properties and values to match:

appstax.find("friends", {gender:"male", hometown:"Oslo"})
       .then(function(objects){
           // objects contain all male friends from Oslo
       })
       .fail(function(error) {
           // Request failed. error.message contains explanation of what went wrong
       });

... or you can use the generic query function:

appstax.find("friends", function(query) {
    query.string("gender").equals("male");
    query.string("hometown").equals("Oslo");
}).then(function(objects) {
    // objects contain all male friends from Oslo
}).fail(function(error) {
    // Request failed. error.message contains explanation of what went wrong
});

Searching string values

If you want your users to search your data you can use search() and provide the string values to search for:

appstax.search("friends", {description:"music"})
       .then(function(objects){
           // objects contain friends with 'music' in their description
       })
       .fail(function(error) {
           // Request failed. error.message contains explanation of what went wrong
       });

... or you can use the generic query function:

appstax.find("friends", function(query) {
    query.string("description").contains("music");
}).then(function(objects) {
    // objects contain friends with 'music' in their description
}).fail(function(error) {
    // Request failed. error.message contains explanation of what went wrong
});

You can also easily search for the same value in many properties at once:

appstax.search("articles", "music", ["title", "content", "tags"])
       .then(function(objects){
           // objects contain articles with 'music' in title, content or tags
       })
       .fail(function(error) {
           // Request failed. error.message contains explanation of what went wrong
       });

Handling users

Security note: Make sure you review the users collection permissions in the Admin UI. If you are using email addresses as usernames or are otherwise storing sensitive information in user objects, set the "read" permissions to "owner" or "admin" level.

Signup

Create a HTML form and call .signup():

var username = ""; // Replace the blank strings
var password = ""; // with values from HTML form

appstax.signup(username, password)
       .then(function(user) {
           // user was successfully signed up
       })
       .fail(function(error) {
           // Something went wrong (like username already taken.)
           // You should show error.message to the user.
       });

You can also add custom properties to the signup form. These values will be saved to the users collection, so remember to add these as columns in the Databrowser as well:

var username = "";
var password = "";
var properties = {
    fullname: "",    // Read from another form field
    email: ""        // Read from another form field
}

appstax.signup(username, password, properties)
       .then(function(user) {
           // ... user was successfully signed up
       })
       .fail(function(error) {
           // Request failed. error.message contains explanation of what went wrong
       });

Login

Just like with signup, you create a HTML form and make a simple method call:

var username = ""; // Replace the blank strings
var password = ""; // with values from HTML form

appstax.login(username, password)
       .then(function(user) {
           // ... user was successfully logged in
       })
       .fail(function(error) {
           // Something went wrong (like wrong username or password.)
           // You should show error.message to the user.
       });

Social Login

If you want to let your users log in with a social network like Facebook, you create a login button and call this code in your event handler:

appstax.login({provider: "facebook"})
       .then(function(user) {
           // The user is logged in with a Facebook account
       })
       .fail(function(error) {
           // Something went wrong. The user may have declined 
           // access for your app or closed the popup window.
       });

Please read our Social Login Guide for a complete description of how to use Social Login.

Logout

The session will be invalidated on the server and later calls to appstax.currentUser() will return null:

appstax.logout();

Add more data to user objects

You can add extra properties to user objects. This data will be saved in a special users collection, so make sure you add the appropriate columns in the Databrowser first:

// This example assumes the user is already logged in
var user = appstax.currentUser();
user.gender = "Male";
user.hometown = "New York";
user.save();

The .save() method returns a promise just like regular objects:

user.save()
    .then(function(user) {
        // ... user was saved
    })
    .fail(function(error) {
        // ... something went wrong
    });

Password reset

You can set up a password reset flow by using the requestPasswordReset and changePasssword functions.

Create a HTML form where user can provide an email address, and start the process:

var email = ""; // Replace with email from form

appstax.requestPasswordReset(email)
    .then(function() {
        // password reset email has been sent to user
    })
    .fail(function(error) {
        // ... something went wrong
    });

An email will be sent to the user containing a code that can then be used to specify a new password. You need to log in to the AppStax Admin UI to set up the contents of this password reset email.

Now set up a second HTML form where the user can use the received code to set a new password:

var options = {
    username: "", // Get these values from your form
    password: "", // Get these values from your form
    code:     "", // Get these values from your form
    login: false  // Set this to true to automatically log in
}

appstax.changePassword(options)
    .then(function(user) {
        // Password was successfully changed.
        // user will be set if you specified login: true
    })
    .fail(function(error) {
        // ... something went wrong
    });

Note that the pin code will only be valid for 10 minutes. Note also that password reset currently only works if users provide emails as their usernames.

Controlling access to objects

When you create new collections, all users can create and read objects from it. While this enables you to get started quickly, it may not be what you want in an app with authenticated users. To restrict access to objects and only allow explicit permissions per object, log in to the Admin UI and set the create and write collection permissions to "only owners of the objects". You can then use the API described below to grant object permissions.

Sharing objects between users

For two users to share an object, the user that created it must grant permissions on the object with the username of the other user:

// You must provide the means for the user to input a username 
// to share with (like a text field or a friend list.)
// In our example the object will be shared with a user "buddy"
var username = "buddy";

var note = appstax.object("notes");
note.grant(username, ["read", "update"]);
note.save();

The example creates a note and gives "read" and "update" permissions, but "buddy" will not be able to delete the note or give anyone else access. The following table lists all permissions a user could have for an object:

With permission ... ... user is able to
"read" Load the object and view all its properties. It will be included in queries.
"update" Change property values on the object and save it.
"delete" Delete the object from storage.
"grant read" Grant and revoke "read" access to others.
"grant update" Grant and revoke "update" access to others.
"grant delete" Grant and revoke "delete" access to others.

The previous example granted permissions on a new object, but of course you can do it on existing objects as well. Just remember to call .save() to persist the permission:

appstax.find("posts", "123-4567")
       .then(function(object) {
           object.grant("buddy", ["read"]);
           object.save();
       });

Revoking permissions

Removing someones access to an object is just as easy. If "buddy" has "read" and "update" permissions, the following would leave him with just "read" permissions:

note.revoke("buddy", ["update"]);
note.save();

Public access

In some applications you will want everyone, including anonymous users, to see or interact with objects. Some use cases are wikis, blogging and public photo sharing applications. Objects have special methods for granting and revoking public access:

// Publishing an blog post
post.grantPublic(["read"]);
post.save();

// Removing post from public
post.revokePublic(["read"]);
post.save();

// Creating a wiki article anyone can edit
article.grantPublic(["read", "update"]);
article.save();

Working with files

You add files to your application by using the file column type in the Databrowser and the appstax.file() method in the SDK. Let's say you add an image column to your messages collection. You then let your user upload an image like this:

// HTML form contains an attachment input field
<input type="file" id="imageInput" />

// JavaScript creates a message ...
var message = appstax.object("messages");
message.text = "Take a look at this!";

// ... and assigns the input file to the image property
var imageInput = document.getElementsById("imageInput");
message.image = appstax.file(imageInput.files[0]);

// Saving the object also uploads the file(s) belonging to the object
message.save()
       .then(function(object) {
           // the message is saved and the image uploaded
       });

Showing upload progress

If you want to display the upload progress of your files, you can use the .progress() method on the returned promise:

// You can use progress element in your HTML ...
<progress max="100" value="0" id="uploadProgress"/>

// ... and update it in a progress handler
message.save()
       .then(function(object) { })
       .progress(function(progress) {
           var element = document.getElementById("uploadProgress");
           element.value = progress.percent;
       });

Image preview before uploading

File properties have a .preview() method that creates a data url to use for preview. Generating the url is async, so it returns a promise:

var image = appstax.file(imageInput);
image.preview().then(function() {
    // image.url now contains a data url 
    // you can use in a <img src="{{data-url}}"/>;
});

Please note that .preview() does not work on files that have already been uploaded. Read on to see how to use files after uploading.

Using image files in HTML

To display images in HTML you use the file.url property in the src attribute:

// Given a "articles" collection with a "mainImage" file column 
// and a "heading" string column. We use Mustache.js to render a 
// list of images and headings into a <div id="output"> element

appstax.findAll("articles").then(function(objects) {
    var output = document.getElementById("output");
    var template = '<ul>{{#articles}}' +
                     '<li><h2>{{heading}}</h2>' +
                     '<img src="{{mainImage.url}}"/></li>' +
                   '{{/articles}}</ul>';
    output.innerHTML = Mustache.render(template, {articles:objects});
})

Getting smaller image sizes

When displaying image files in your app it is often desirable to load a lower resolution image than the user originally uploaded. The file.imageUrl() method gives URLs to resized versions of the images:

// Downsized to 250px wide, height is automatically set to keep aspect ratio
mainImage.imageUrl("resize", {width:250}); 

// Downsized to 120px tall, width is automatically set to keep aspect ratio
mainImage.imageUrl("resize", {height:120}); 

// Downsized to maximum 250px wide or 120px tall
mainImage.imageUrl("resize", {width:250, height:120}); 

// Downsized and cropped to exactly 250px wide and 120px tall
mainImage.imageUrl("crop", {width:250, height:120});

To create a download link, just put the file.url property in the href attribute of a HTML link. You can also use the file.filename property to display a file name:

// Given a "messages" collection with a "attachment" file column 
// and a "content" string column. We use Mustache.js to render a 
// list of message text and attachment links into a <div id="output"> element

appstax.findAll("messages").then(function(objects) {
    var output = document.getElementById("output");
    var template = '<ul>{{#messages}}' +
                     '<li><p>{{content}}</p>' +
                     '<a href="{{attachment.url}}">' +
                     '{{attachment.filename}}</a></li>' +
                   '{{/messages}}</ul>';
    output.innerHTML = Mustache.render(template, {messages:objects});
})

Relations

As your data model grows, you will often need objects to reference other objects. There are two types of relations you can add:

  • A single relation adds a property that can reference one other appstax object.
  • An array relation adds an array where you can add as many appstax objects as you like.

If you come from a database modelling background you will find single relations useful for creating one-to-one and many-to-one models, while array relations work well for one-to-many and many-to-many models.

Single relations (one-to-one / many-to-one)

Given two collections invoices and customers, we can add a .customer single relation to the invoices collection in order for an invoice to include customer information without duplicating the details into all the invoices.

To create the relation in the databrowser: Select the invoices collection, click the relations button.

To create the relation on the command line, run appstax relation invoices.customer customers

Let's create a new customer and his first invoice and save them both with .saveAll():

// create customer and invoice objects
var customer = appstax.object("customers");
var invoice  = appstax.object("invoices");
customer.name = "Bill Buyer";
customer.address = "Money Hill";
invoice.total = 199;

// assign the related customer object and save both objects
invoice.customer = customer;
invoice.saveAll().then(function() {
  // ... both objects have been saved
});

Choosing between .save() and .saveAll()

An object needs to be saved before it can be used in a relation, and calling .save() will fail if it finds relations with unsaved objects. You can choose to call .save() on each object yourself, or call .saveAll() to have all the related objects saved automatically.

To load invoices and include related customer data you can use the expand option with .find()/.findAll():

appstax.findAll("invoices", {expand:true})
       .then(function(invoices) {
         // now you can use invoices[0].customer.name etc.
       });

You can find all invoices for a specific customer with a property matching query:

var myCustomer = ... // myCustomer has previously been loaded from the backend
appstax.find("invoices", {customer:myCustomer})
       .then(function(invoices) {
         // ... render the invoices in your view
       });

// add the expand option to include the related customer objects:
appstax.find("invoices", {customer:myCustomer}, {expand:true})
       .then(function(invoices) {
         // ... render the invoices in your view,
         // including invoices[0].customer.name etc.
       });

Loading nested relations

When adding the expand:true option the objects returned will include the first level of related objects. Those related objects may themselves have relations to other objects. You can include nested relations by specifying the number of levels you want:

{expand: true} // one level
{expand: 1}    // one level
{expand: 2}    // two levels
{expand: 10}   // ten levels
... etc ...

If the customer collection in the previous example included a relation to a contacts collection, you could include all contacts for an invoice like this:

appstax.find("invoices", {customer:myCustomer}, {expand:2})
       .then(function(invoices) {
         // ... render the invoices in your view,
         // including invoices[0].customer.contacts[0].email etc.
       });

Array relations (one-to-many / many-to-many)

Given two collections blogs and posts, we can connect the posts to a blog by adding a .posts[] array relation to the blog collection.

To create the relation in the databrowser: Select the blogs collection and click the relations button.

To create the relation on the command line, run appstax relation blogs.posts[] posts

Let's create a new blog and a couple of posts:

// create the blog
var blog = appstax.object("blogs");
blog.title = "Zen";

// create a post
var post1 = appstax.object("posts");
post1.title = "My first post";
post1.content = "...";

// create another post
var post2 = appstax.object("posts");
post2.title = "My second post";
post2.content = "...";

// add posts to blog and save
blog.posts = [post1, post2];
blog.saveAll(); // saves both posts and blog

We can now load all blogs, including posts, with a query using .find()/.findAll() and expand:

appstax.findAll("blogs", {expand:true})
       .then(function(blogs) {
         // now you can use 
         // blogs[0].title
         // blogs[0].posts[0].title
         // ... etc ...
       });

Relations and access control

Querying relations

Querying objects with relations enforces "read" permissions. For example, a blog.posts[] relation may have many posts, but a call to .find("blog", "<blogid>", {expand:true}) will only include those posts the current user has "read" access to.

The kind of access restrictions you want on objects will affect the relations model you should use. Adding or removing related objects requires "create" or "update" permissions on the object where the relation is stored. In practise, this often means choosing between implementing one-to-many or many-to-one using array or single relations.

Example 1: If you have a blog data model with posts and comments collections, you may let users comment on posts by adding a post.comments[] array relation. But in order for users to add comments in this model they would need "update" permissions on the posts object. This is probably not what you want, as it would allow all users to change the contents of the blog post. Instead you should use a comment.post single relation, which will allow anyone to add a comment referencing any post.

Example 2: On the flip side of example 1 you may have blogs and posts in your data model and only want the owner of a blog to add posts to it. If you do this with a post.blog single relation, anyone would be able to add posts to the blog. If you instead choose a blog.posts[] array relation, adding posts will be restricted to users with "update" permissions to the blog.

Realtime Channels

Realtime is currently in beta. Try it out and drop us an email at support@appstax.com for comments and suggestions!

Channels use WebSockets to provide bidirectional communication between the backend and your app.

There are three types of channels: public channels, private channels, and object channels:

  • Public channels are open to all users. Anyone can send and receive messages.
  • Private channels lets you grant or revoke read/write access to specific users.
  • Object channels lets you use sockets to observe changes to a specific collection.

A channel identifier consists of its type, a slash, and then an name given by the developer:

// A public channel called 'foobar':
appstax.channel("public/foobar")

// A private channel identified by an email:
appstax.channel("private/james@example.com")

// A channel for updates to the "notes" collection:
appstax.channel("objects/notes")

Public channels

Public channels are the simplest type of channel. Any user can subscribe to a public channel, read all messages sent to that channel, and send new messages to the other subscribers. Here's an example:

To send a message, call the send method. You can send plain string, or a javascript object:

var ch = appstax.channel("public/messages");
ch.send({text:"Hello World!"});

To receive messages, register an event handler with the on method:

var ch = appstax.channel("public/messages");

ch.on("message", function(event) {
  // Called when a message arrives. The content is in event.message.
});

ch.on("error", function(event) {
  // Something went wrong
});

For complete example of public channels in use, check out our chat tutorial.

Private channels

Private channels work just like public channels, but with access control. You can use grant and revoke to control read and write access.

// Private channels have their own prefix:
var ch = appstax.channel("private/john");

// Use grant/revoke to control access:
ch.grant("jane", ["read", "write"]);

// You can now send messages:
ch.send("Hello Jane!");

Security

The security in private channels come from two features:

  • The owner of a channel (the first user to use a private/... channel identifier) has complete control over who can send and receive messages on that channel.
  • The receiver of a message can verify who sent a message by checking the event.sender property. This is set by the appstax realtime server and will always contain the username of the user that sendt the message.

The channel identifier can NOT be used to verify who is on the other end of a channel. This is because all authenticated users are free to create any channel identifier they wish. Use the security features described above instead.

Listening to multiple channels with wildcard patterns

You can listen to many channels at once by ending the channel identifier with a *

var ch = appstax.channel("public/news/*");

ch.on("message", function(event) {
  // Messages will be received from 
  // public/news/economy and public/news/technology, but not from
  // public/notifications or public/messages
});

This also simplifies listening to private channels because the receiver does not have to know the name of the channel to listen to:


// Jane has created this channel:
var ch1 = appstax.channel("private/chat/jane");
ch1.grant("john", ["read"]);

// Jim has created this channel:
var ch2 = appstax.channel("private/chat/jim");
ch2.grant("john", ["read"]);

// and John can listen to both:
var ch3 = appstax.channel("private/chat/*");
ch3.on("message", function(event) {
  // Here John will receive everything he's allowed to receive
  // on any channel starting with "private/chat/..."

  // event.sender will tell which user sent the message (username)
  // event.channel contains the full channel identifier used to send the message
});

Object channels

Object channels lets your observe changes to a collection without resorting to periodic polling.

In the following example, the app listens for any additions, updates or deletions to objects in the notes collection:

// channel identifier is objects/<collection>
var ch = appstax.channel("objects/notes")

ch.on("object.created", function(event) {
  // a new object was added to the collection
  // access it with event.object
});

ch.on("object.updated", function(event) {
  // event.object was updated
});

ch.on("object.deleted", function(event) {
  // event.object was deleted
});

These channels lets you display news or react to changes to objects in your collections, without having to check the server periodically for new updates. Unlike public and private channels, you can't send directly to an object channel. Use the normal appstax object methods like object.save() to perform changes to the collection.

If you're only interested in certain objects, you can add a filter to the channel:

var ch = appstax.channel("object/notes", "title like 'Hello%'");

ch.on("object.created", function(event) {
  // only notes with title starting with Hello will arrive here
});

The filter syntax is the same as the one used when querying objects.

Realtime Model

Realtime is currently in beta. Try it out and drop us an email at support@appstax.com for comments and suggestions!

The Realtime Model is an easy and powerful way to work with live data into your app. If you're currently using a MV* or databinding framework like AngularJS or React, the Realtime Model will give you live data data storage with a minimal amount of code.

Configure the model

// create a model object
var model = appstax.model();

// specify which collections to watch
model.watch("todos");
model.watch("messages");

model.on("change", function() {
  // if your MV* framework has an update/render
  // method, call it here
});

Each call to .watch(<collection>) will create an array property model.<collection>[] for the collection you are watching. After initial data loading they contain the objects from your collections, and will then be updated live each time a change occurs in your DataStore.

Watching collections

You can specify sort order when watching a collection. Default orderering is ascending; prepend the option with - to sort descending.

model.watch("todos", {order: "-created"}); // By created date descending. This is the default.
model.watch("todos", {order: "title"});    // Alphabetically by a string property.
model.watch("todos", {order: "views"});    // Numerically by a number property.

Watching login state and user data

Watch the special name currentUser to keep track of the currently logged in user.

model.watch("currentUser");

The model.currentUser property will be undefined until a user has logged in. While logged in it will contain the current user's object.

Update the model

The model will update itself when data changes on the server, so just call object.save(), object.remove() etc. as described in Working with objects. You should not make changes directly to the model's array properties. Instead, the objects will be automatically added/removed in response to your object calls.

// To add an object, create it and call save()
// Dont push the object to model.todos[].
var todo = appstax.object("todos");
todo.title = "Do work!";
todo.save();

// To remove an object, call .remove() on it.
var todo = model.todos[x];
todo.remove();

AngularJS examples

For a complete AngularJS example, read the Realtime Model Tutorial.

Put the model on the $scope of a controller:

$scope.model = appstax.model();
$scope.model.watch("todos");
$scope.model.watch("messages");

$scope.model.on("change", function() {
  $scope.$apply();
});

Use the model properties in your template:

<ul>
  <li ng-repeat="todo in model.todos">
    {{ todo.title }}
  </li>
</ul>
<ul>
  <li ng-repeat="message in model.messages">
    {{ message.text }}
  </li>
</ul>

Add and remove objects with object methods:

$scope.addTodo = function(title) {
  var todo = appstax.object("todos");
  todo.title = title;
  todo.save();
}

$scope.removeTodo = function(todo) {
  todo.remove();
}

React examples

For a complete React example, read the Realtime Model Tutorial.

Create the model and use it when rendering your app:

var model = appstax.model();
model.watch("todos");
model.on("change", render);

function render() {
  React.render(
    <TodoApp model={model}/>
  );
}

And when rendering your components:

TodoItem = React.createClass({

  render: function() {
    var todos = this.props.model.todos;
    var todoItems = todos.map(function (todo) {
      return (
        <TodoItem todo={todo} />
      );
    });
    return (
      <ul>{todoItems}</li>
    );
  }

});