Welcome

Welcome to Backendless, a powerful API services platform. The platform can be used to build and run mobile and web applications. Backendless consists of built-in, general purpose API services and also supports deployment of custom, developer-defined services and functions. The built-in API services support common backend tasks and operations such as user login and logout, password recovery, data persistence, file upload, etc. Developer-defined services and functions can be used for custom server-side business logic. The diagram below illustrates major components of the Backendless architecture:

backendless-architecture

The sections below describe individual service APIs. It is recommended to go through the Setup section first to configure your environment.

Quick Start Guide

This guide will help you develop a basic mobile application up and running with the Backendless 4 backend.

 

Once the product is installed, there are two URLs to keep in mind:

1. Backendless Console URL - available at http://HOSTNAME:8080, where HOSTNAME is the IP address or the hostname confirmed/entered during  the installation. It is not recommended to use localhost if you plan to run your application in a device emulator.
2. API Endpoint URL - this is the URL where the server accepts and processes the API requests. The API endpoint URL is the same as console URL with /api appended at the end. For example, http://HOSTNAME:8080/api. When Backendless Pro is running, you can confirm that the API endpoint is working, by entering it in a browser. You should see the following (or similar) response:
api-endpoint-response

 

Follow the steps below to build your first app with Backendless:

1. Login to Backendless Console. The username and password are what you entered during the installation process. If you cannot remember the login, click the Register link on the Backendless Console login screen and create a new developer account.
 
2. Select or create an app. If you do not have any applications in your account, you will be prompted to create one.
 
3. Click the Download Project Template button:
download-proj-template.zoom50
 
4. Double click the JavaScript icon, then double-click the HTML-JS icon to download a project for JS:
html-js-project
 
5. The downloaded ZIP contains an IntelliJ IDEA/WebStorm project, however, since the structure of the project is very straight-forward, you can open it with any IDE. For the sake of simplicity this guide assumes the project is opened with IntelliJ IDEA.
 
6. The project contains an HTML and a JS file:
js-project
 
7. The HTML file imports the version 4 of the Backendless JS library:
<script src="//api.backendless.com/sdk/js/latest/backendless.js"></script>

 
8. The JS code located in the app.js file contain library initialization code (Backendless.initApp) as well as the code which stores an object in the database of your Backendless backend:
var APP_ID = '6F70C24E-6B29-DD1D-FFA9-74BC15FE0900';
var API_KEY = '479F3EA2-87F6-DF1C-FF3A-86EA4B1D4F00';

Backendless.initApp(APP_ID, API_KEY);

Backendless.Data.of( "TestTable" ).save( { foo:"bar" } )
    .then( function( obj ) {
        console.log( "object saved. objectId " + obj.objectId )
    } )
    .catch( function( error ) {
        console.log( "got error - " + error )
    })

 
9. The code is ready to run without any changes. When you open the HTML file in a browser (make sure the file is loaded from a web server), you will see the following output in the browser's console window:
running-js-project
 
10. To verify the object in Backendless, return to Backendless Console, and click the Data icon. The TestTable table shows the persisted object:
saved-object.zoom50

Client-side Setup

To get access to the Backendless services, JavaScript applications must reference the backendless.js library. The library can be retrieved using any of the approaches listed below:

Use NPM:

npm i backendless

Reference the library with either one of the URLs below:

non-compressed library (mapped to the latest released version):
https://api.backendless.com/sdk/js/latest/backendless.js
 
minified/compressed library:
https://api.backendless.com/sdk/js/latest/backendless.min.js
 
TypeScript library:
https://api.backendless.com/sdk/js/latest/backendless.min.map
https://api.backendless.com/sdk/js/latest/backendless.d.ts
 
Specific versions of the library can be referenced with the following URL format:
https://unpkg.com/backendless@4.0.8

 

Before the JavaScript client uses any of the APIs, the code must initialize the Backendless Application using the following call:

Backendless.initApp( application-Id, api-key )

Application ID and API Key

Values for the application-id and api-key headers must be obtained through the Backendless Console:

1. Login to your account and select the application.
2. Click the Manage icon from the vertical icon-menu on the left.
3. The App Settings section is selected by default. The interface contains values for Application ID and API keys for each supported client-side environment.
4. Use the Copy icon to copy the value into the system clipboard.
appsettings-keys.zoom60

Make sure to use the "JavaScript API Key" for the api-key argument.

 

Migration from 3.x to 4.x

Version 4 of Backendless introduces some changes in the client-side API. This section of the developer guide describes the changes which should be made in the client application to migrate it to Backendless version 4.x.

 

The general process of application migration from 3.x to 4.0 is:

1. Create an app in the version 4 of Backendless.
2. Migrate data and files from the 3.x application.
3. Make changes in the API usage in the cloud application (if applicable).

Version Differences

Functional Area

Version 3.x

Version 4.0

Application identification (REST Only)

REST requests must include the application-id and secret-key HTTP headers with the API requests.

Application ID and API key (previously known as secret key) are included into the endpoint URL.

Application initialization API.
This is a mandatory  method call required to initialize a native SDK implementation in a client app. Applies to all SDKs except for REST.

the initApp argument list includes version number.

the initApp argument list does not include version number. See Migrating App Initialization API for additional information.

Data Query.

The API for configuring data query parameters such as sorting, paging and identifying columns of the related objects to return in the response.

Data retrieval API (the "find" operation in the Data service) uses the Backendless.DataQuery

Backendless.DataQuery has been replaced with DataQueryBuilder

 

See Migrating Data Query API for additional details

Retrieval of data collections.
This applies to retrieving data objects, geopoints and listing of files.

Native SDKs
All SDK implementations use the BackendlessCollection class to represent a collection of data from the backend. The class provided a paging mechanism enabling retrieval of additional pages of data.

Native SDK and REST

BackendlessCollection is completely eliminated. Instead the backend returns an array of objects (represented as a native collection in the native SDKs). The totalObjects property is replaced with a separate API (see below)

REST
REST operations returned a JSON structure containing an array of objects (paged) as well as the totalObjects property

Object counting.
This is a mechanism used by the server to calculate:

total number of objects in a table, optionally satisfying a search query.
total number of files in a directory listing, optionally matching a pattern.
total number of geopoints in a category, optionally satisfying search query (search in radius, rectangular area and/or search query).

Object counting is coupled with collection retrieval (see the row above). Total number of data objects/files/geopoints is returned to the client through the totalObjects property in the response object (BackendlessCollection for native SDKs)

Since BackendlessCollection is removed and thus is never returned in the API operations, there is a new API to obtain object/file/geopoint counts.

 

See Migrating Object/File counts API for additional information.

Object relationship management.
This is a mechanism for establishing or breaking down relations between two or more data objects using the API. Once a relationship is established, client application can use data retrieval API to fetch related object(s) for a parent object.

To establish a relationship between a parent object and the related children, client application must save the parent object instance which references the child object(s). Backendless processes the entire object hierarchy, determines which child objects must be saved/updated, persists them and establishes object relationship between the parent and the children. To remove a relationship, parent object instance must be fetched from the server with its related child objects. Client app deleted from the collection any children for which the relationship must be removed and saves back to the server the parent object.

New API is introduced for:

1. establishing a relationship between two or more objects. See the "Set" API.
2. adding related objects to an existing relation. See the "Add" API.
3. removing objects from a relation. See the relation removal API.

 

See Migrating Related Objects API for additional information.

 

Migrating App Data

The process of migrating application data and files from 3.x to 4.0 is:

 

1. Export application from Backendless Cloud. Login to Backendless Console for your Cloud-based backend and select the app. You will see the "Migrate to Backendless 4.0" button:
migrate-button.zoom79
 
2. Click the Migrate to Backendless 4.0 button. You will see the following popup. If you have more than one version in your Backendless Cloud backend, make sure to select the version for which Backendless will prepare a migration archive. The reason a version must be selected is because 4.0 uses the one-version-per-app approach:
migration-popup
 
3. Select a version and click "Create migration archive for 4.0". The first step in replicating your app in 4.0 is creating a "migration archive". Backendless creates two archives, one with data and another with files. The data archive contains the entire backend. It includes, application settings, data table schemas, data objects, users, security roles, all security permissions, business logic, geopoints and geofences.  To initiate creation of the migration archives, click the "Create migration archive for 4.0" button.
 
4. Download archives. Backendless starts preparing the migration archive and will send out an email as soon as the data and file archives are ready. The archives are placed into the /migration-to-4 directory in the File storage of your app. You will see two files with the names matching the following patterns:

<versionName>-data-<timestamp>.zip
<versionName>-files-<timestamp>.zip

 

5. Create an application in the version 4.0 of the Backendless. You can do that by switching to version 4 (see the arrows icon next to the Backendless logo in the developer console) and creating an app there.
6. Upload file archive into 4.0 app. Open Backendless Console for 4.0 and select the app where you will import the data/files. It is recommended to start with a blank/new app to avoid any conflicts during the import. Select the Files section in Backendless console and upload the "files" archive into the root directory. This can be done either by using the Upload File menu or by drag-and-drop of the file into Console. Once the file is uploaded, the console will appear similar to the image below:
uploaded-file-archive.zoom84
 
7. Upload "Data" archive into 4.0 app. Click the Manage icon, then click Import. Click the Browse button located next to "Backendless app" and browse to the "data" archive downloaded from Backendless Cloud. Once the file is selected, click the IMPORT button at the bottom of the screen to initiate the import process:
start-import-v4.zoom56
 
8. Migration is complete. Once Backendless 4.0 finishes processing the data, it will send out an email informing you of the results. You can see the migration progress or its status after it is complete in the migration log available in the Files section of your app:
migration-logv4.zoom90

App Initialization

Support for application versions has been removed in Backendless v4. As an alternative to versions, it is recommended to create multiple copies of the same application and export/import data between the apps. As a result of the version support removal, application initialization in the code does not require version name in the initApp call.

Version 3.x:

Backendless.initApp( applicationID, secretKey, versionName );

Version 4.x:

Backendless.initApp( applicationID, apiKey );

Notice that version 4 uses the term "API key" for the concept we used to call "secret key".

Data Query

Data service find operation can accept an argument with additional options instructing the server to sort data, retrieve paged results and return related child objects. The example below demonstrates the usage of the data query argument in versions 3.x and 4.x:

Version 3.x:

var contactStorage = Backendless.Data.of( "Contact" );
var dataQuery = new Backendless.DataQuery();

// set where clause
dataQuery.condition = "age > 30";

// prepare options object (for relations, sorting, pageSize and offset)
dataQuery.options = {}

dataQuery.options.relations:[ "address","preferences" ] };

// request sorting
dataQuery.options.sortBy = [ "name" ]

// set offset and page size
dataQuery.options.pageSze = 20;
dataQuery.options.offset = 40;

contactStorage.find( dataQuery, new Backendless.Async(
  function( contactsCollection ) {
    console.log( "retrieved " + contactsCollection.data.length + " contacts" );
  }
));

Version 4.x:

var contactStorage = Backendless.Data.of( "Contact" );
var queryBuilder = Backendless.DataQueryBuilder.create();

// set where clause
queryBuilder.setWhereClause( "age > 30" );

// request related objects for the columns
queryBuilder.setRelated( [ "address", "preferences" ] );

// request sorting
queryBuilder.setSortBy( [ "name" ] );

// set offset and page size
queryBuilder.setPageSize( 20 );
queryBuilder.setOffset( 40 );

contactStorage.find( queryBuilder )
 .then( function( contactsArray ) {
    console.log( "retrieved " + contactsArray.length + " contacts" );
  });

 

Additional resources:

Related Objects

Establishing object relationship:

The process of establishing a relationship between multiple objects in Backendless 3.x is by saving the parent object which references its child(ren) in the same object hierarchy/tree. Version 4 changes the mechanism by introducing a dedicated operation which establishes a relationship between the objects. The example below stores two objects in Backendless Data service: Contact and Address and then sets up a relationship between them:

Version 3.x:

var contactStorage = Backendless.Data.of( "Contact" );

var joeThePlumber = {
    name: "Joe",
    age: 27,
    phone: "1-972-5551212",
    title: "Plumber",
    ___class: "Contact"
};

var address = {
     street: "123 Main St.",
     city: "Denver",
     state: "Colorado",
     ___class: "Address"
}

contactStorage.save( joeThePlumber, new Backendless.Async(
  function( savedContact ) {
    contactStorage.save( address, new Backendless.Async(
      function( savedAddress ) {
        savedContact.address = [ savedAddress ];
        contactStorage.save( savedContact, new Backendless.Async(
          function( contactWithRelation ) {
            console.log( "contact now has related address" );
          }
        ));
      }
    ));
  }
));

Version 4.x:

var contactStorage = Backendless.Data.of( "Contact" );
var addressStorage = Backendless.Data.of( "Address" );

var joeThePlumber = {
    name: "Joe",
    age: 27,
    phone: "1-972-5551212",
    title: "Plumber",
    ___class:"Contact"
};

var address = {
    street: "123 Main St.",
    city: "Denver",
    state: "Colorado",
    ___class: "Address"
}

Promise.all([
    contactStore.save(joeThePlumber),
    addressStore.save(address)
])
.then(function(results) {
   var savedContact = results[ 0 ];
   var savedAddress = results[ 1 ];

   // for 1:1 relations, use the ":1" notation instead of ":n"   
   return contactStore.setRelation( savedContact, 'address:Address:n', [ savedAddress ] );
})
.then( function(){
  console.log('the relation has been set')
}

Deleting/breaking object relationship:

Similar to establishing a relationship between objects, Backendless 3.x relied on the update operation which sends the parent object to the server with an updated list of child objects. Any children removed from the collection on the client-side, are removed on the server as a result of the update call. Version 4 introduces a dedicated method to remove a relationship between objects. The example below removes a relationship between a Contact object and the first related Address object:

Version 3.x:

var contactStorage = Backendless.Data.of( "Contact" );

// get the parent object
contactStorage.findById( "XXXX-XXXX-XXXX-XXXXX", new Backendless.Async( 
  function( contact ) {

    // load related objects for the "address" column
    contactStorage.loadRelations( contact, ["address"], new Backendless.Async( 
      function( updatedContact ) {
        // remove first element from the collection
        updatedContact.address = updatedContact.address.splice( 0, 1 )

        // save contact back
        contactStorage.save( updatedContact, new Backendless.Async(
          function( result ) {
            console.log( "contact has been updated, relation deleted");
          }));
      }));     
   }));

Version 4.x:

var contactStorage = Backendless.Data.of( "Contact" );

// get the parent object
contactStorage.findById("XXXX-XXXX-XXXX-XXXXX")
  .then(function(contact) {

    // load related objects for the "address" column
    var loadRelationsQueryBuilder = Backendless.LoadRelationsQueryBuilder.create();
    loadRelationsQueryBuilder.setRelationName("address");

    contactStorage.loadRelations(contact, loadRelationsQueryBuilder)
      .then(function(addresses) {

        // break relation between contact and address
        contactStorage.deleteRelation(contact, "address", [addresses[0]]).then(function() {
          console.log("relation has been deleted");
        });
      });
  });

 

Additional resources:

Object/File Counts

Backendless version 4.x introduces new API to calculate total number of objects and files in a remote collection. In the previous version of Backendless, that number was included as a property of the returned collection. See the examples below:

Version 3.x

var contactStorage = Backendless.Persistence.of( "Contact" );
var dataQuery = new Backendless.DataQuery();
dataQuery.condition = "age > 21";
contactStorage.find( dataQuery, new Backendless.Async(
  function( contactCollection ) {
    console.log( "Total objects matching query - " + 
                 contactCollection.totalObjects );
  }
));

Version 4.x

var contactStorage = Backendless.Persistence.of( "Contact" );
var queryBuilder = Backendless.DataQueryBuilder.create();
queryBuilder.setWhereClause('age > 21')
contactStorage.getObjectCount( queryBuilder )
  .then( function( count ) {
    console.log( "Total objects matching query - " + count );
  });

 

Additional resources:

Data/File Paging

Data paging in Backendless 3 is built into the collection returned from the data retrieval operations. Version 4 changes the mechanism by shifting paging operations to the data query class. See the examples below:

Version 3.x

Consider the following data table in Backendless:

sample-data-paging.zoom50
The code for retrieving the data using default page size (10 objects):

var callback = new Backendless.Async(handleResponse, handleFault);

function handleResponse (restaurants) {
   printRestaurants( restaurants.data );
   
   if( restaurants.nextPage != undefined )
     restaurants.nextPage( callback );
   else
     console.log( "Reached end of collection" );
}

function printRestaurants( restaurantsCollection ) {
   console.log( "Loaded " + restaurantsCollection.length + 
                " restaurants in the current page" );
   
   for( var i in restaurantsCollection )
     console.log( "\t" + restaurantsCollection[ i ].name );
}

function handleFault (backendlessFault) {
    console.log("Server reported an error - ");
    console.log(backendlessFault.message);
    console.log(backendlessFault.statusCode);
}

Backendless.Persistence.of("Restaurant").find(callback);
The code produces the following output:

Loaded 10 restaurants in the current page
 Artin's Grill
 Istanbul
 Magic Wok
 Olive Garden
 Platia
 Cantina Loredo
 Wendy's
 Chili's
 Kenny's Burgers
 Tony Roma's
Loaded 1 restaurants in the current page
 McDonnald's
Reached the end of collection

Version 4.x

Identical to the screenshot from 3.x above, consider the following data in version 4 of Backendless:

var queryBuilder = Backendless.DataQueryBuilder.create();
getRestaurants( queryBuilder );

function getRestaurants( queryBuilder ) {
  Backendless.Persistence.of("Restaurant").find( queryBuilder )
 .then( handleResult )
 .catch( handleError );
}

function handleResult( restaurants ) {
  printRestaurants( restaurants );
  
  if( restaurants.length > 0 ) {
    queryBuilder.prepareNextPage();
    getRestaurants( queryBuilder );
  }
  else {
    console.log( "Reached the end of collection" );
  }    
}

function handleError( error ) {
  console.log( "Server reported an error - " );
  console.log( error.message );
  console.log( error.errorCode ); 
}

function printRestaurants( restaurantsCollection ) {
  console.log( "Loaded " + restaurantsCollection.length + 
                " restaurants in the current page" );
   
  for( var i in restaurantsCollection )
  console.log( "\t" + restaurantsCollection[ i ].name );
}
The code produces the same output as the code for 3.x

Sync and Async Calls

All Backendless APIs for JavaScript can be invoked synchronously (blocking app execution) or asynchronously (non-blocking, response is delivered via a promise). Default implementation of all APIs is non-blocking (methods return a Promise object, see the Mozila doc or Google's primer for the details on the JavaScript Promise API). The promises are supported and enabled by default, mostly in all modern browsers. If you want to support older browsers, consider using polyfill.

 

Every Backendless API function has a synchronous version where the method name ends with "Sync". For example, consider the following two functions:

 

Non-blocking user registration:

function userRegistered( user ) {
    console.log( "user has been registered" );
}
 
function gotError( err ) {
    console.log( "error message - " + err.message );
    console.log( "error code - " + err.statusCode );
}

var user = new Backendless.User();
user.email = "backendlessdeveloper@backedless.com";
user.password = "password";
Backendless.UserService.register(user).then(userRegistered).catch(gotError);

Blocking user registration. Notice "Sync" at the end of the method name:

var user = new Backendless.User();
user.email = "backendlessdeveloper@backedless.com";
user.password = "password";
Backendless.UserService.registerSync( user );
console.log( "user has been registered" );

 

Using non-blocking APIs in web apps is considered a good practice. Blocking APIs are not recommended for the following reasons:
1. Blocking APIs may lead to inferior user experience such as non-responsive UI.
2. Modern web browsers mark blocking XHR requests as 'deprecated' and report usage warnings in the browser's console.

 

It is important to note that Backendless custom business logic - API services, API event handlers and timers (Cloud Code) can use only non-blocking APIs.

Error Handling

When the server reports an error, it is delivered to the client through a fault object, which is an untyped JavaScript object. The fault object has the same structure for both blocking and non-blocking invocations:

{
  "message": value,
  "statusCode": value
} 

where:

message - contains a string value with the description of the error
statusCode - error code as a string value. Currently all the error codes are numbers, however the method returning the error code returns the String type. This is done for future expansion of the error code system which may include characters.

 

The asynchronous calls receive the fault through the catch() function of the returned promise object.

nonBlockingBackendlessAPICall()
  .then( function() {
   })
  .catch( function( error ) {
     console.log("error message - " + err.message);
     console.log("error code - " + err.statusCode);
   });

For the synchronous calls the fault object is thrown as an error which must be handled in a catch( err ) block:

try
{
  blockingBackendlessAPIcallSync();
}
catch( err )
{
  console.log( "Error message - " + err.message );
  console.log( "Error code - " + err.statusCode );
}


Error Codes

Click here for the complete list of Backendless Error Codes

User Service API

Overview

The Backendless User Service provides the functionality related to the user accounts such as user registrations, logins, password recovery and logouts. The core concept which the User Service relies on is the User entity. The structure of the entity is configurable, that is a developer can decide which properties "describe" a user in the context of the application. Typically, properties describing a user are the ones collected during the user registration process.  The User Service provides the API enabling the following functionality for the applications built on top of Backendless:

 

User Registration - Applications use the Backendless' registration API to let the users register and create accounts for subsequent logins. Application developers can restrict access to the server-side resources for specific user accounts or based on roles.
User Login - the API lets the registered users login to establish their identity within the application.
Password Recovery - Backendless supports a complete workflow allowing users to recover lost or forgotten passwords.
User Logout - the API lets the logged in users terminate their session and disassociate their identity from the application.
Updating User Registration - the API supports the operation of updating user information.

Retrieve User Schema

An application can get a list of the properties associated with the user schema by using the following API:

Non-Blocking Method Signature:

Backendless.UserService.describeUserClass()
 .then( function( result ) {
  })
 .catch( function( error ) {
  });

Blocking Method Signature:

Backendless.UserService.describeUserClassSync();

Return Value:

Server returns an array of objects representing user properties (the columns in the Users table). Each object has the following properties:

{
  // name of the property
  "name": value,

  // indicates whether the property is required for user registration
  "required": true or false,

  // property data type
  "type": "STRING"|"STRING_ID"|"DATETIME"|"RELATION"|"INT"|"DOUBLE",

  // default value of the property if one is not provided during registration
  "defaultValue": value or null,

  // indicates whether the property is marked as user identity
  "identity": true or false,
  
  // provides more information about relation columns
  "relatedTable" : if "type" is "RELATION", this property contains the name of the related table
}

Non-blocking Method Example:

// do not forget to initialize the app with the Backendless.initApp( appId, apiKey ) call
Backendless.UserService.describeUserClass()
 .then( function( result ) {
    for( var i in result )
    {
      console.log( "property name - " + result[ i ].name );
      console.log( "\tis property required - " + result[ i ].required );
      console.log( "\tproperty data type - " + result[ i ].type );
      console.log( "\tdefault value - " + result[ i ].defaultValue );
      console.log( "\tis property identity - " + result[ i ].identity );
   }
  })
 .catch( function( err ) {
    console.log( "server returned an error " + err.message );
  });

Blocking Method Example:

// do not forget to initialize your app with the Backendless.initApp( appId, apiKey ) call
 
var props = Backendless.UserService.describeUserClassSync();

for( var i in props )
{
  console.log( "property name - " + props[ i ].name );
  console.log( "\tis property required - " + props[ i ].required );
  console.log( "\tproperty data type - " + props[ i ].type );
  console.log( "\tdefault value - " + props[ i ].defaultValue );
  console.log( "\tis property identity - " + props[ i ].identity );
}

 

User Registration

The user registration API can be used to create user accounts in the application. A registration request must provide a user object as a collection of key/value properties. The collection must contain all the required properties which must include a property marked as identity as well as the "password" property. The default identity property is "email", however, it can be changed in Backendless Console. Additionally, the "email" property is required if the application is configured to confirm email addresses for the registered users.

Non-blocking Method Signature:

Backendless.UserService.register( user )
  .then( function( registeredUser ) {
    })
  .catch( function( error ) {
    });

where:

user - an instance of the Backendless.User class which contains property values for the account registration.

Blocking Call Method Signature:

Backendless.UserService.registerSync( user );

where:

user - an instance of the Backendless.User class which contains property values for the account registration.

Error Codes:

The following errors may occur during User Registration API calls. See the Error Handling section for details on how to retrieve the error code when the server returns an error.

Error Code

Description

2002

Invalid application info (application id or api key)

3009

User registration is disabled for the application

3010

User registration has an unknown property and dynamic properties are disabled for this version of the application

3011

Missing "password" property

3012

Required property is missing

3013

Missing value for the identity property

3014

External registration failed with an error.

3021

General user registration error. Details included with the error message.

3033

User with the same identity already exists

3038

Missing application-id or collection of properties for the registering user

3039

Property "id" cannot be used in the registration call

3040

Email address is in the wrong format

3041

A value for a required property is missing

3043

Duplicate properties in the registration request

8000

Property value exceeds the length limit

 

Non-blocking Method Sample:

// do not forget to initialize your app with the Backendless.initApp( appId, apiKey ) call

function userRegistered( user )
{
  console.log( "user has been registered" );
}

function gotError( err ) // see more on error handling
{
  console.log( "error message - " + err.message );
  console.log( "error code - " + err.statusCode );
}

var user = new Backendless.User();
user.email = "james.bond@mi6.co.uk";
user.password = "iAmWatchingU";

Backendless.UserService.register( user ).then( userRegistered ).catch( gotError );

Blocking Method Sample:

// do not forget to initialize your app with the Backendless.initApp( appId, apiKey ) call

var user = new Backendless.User();
user.email = "james.bond@mi6.co.uk";
user.password = "iAmWatchingU";

try
{
  Backendless.UserService.registerSync( user );
}
catch( err )
{
  console.log( "error message - " + err.message );
  console.log( "error code - " + err.statusCode );
}

Turning Registration Off

User registration can be disabled for a particular version of the application using the Backendless Console:

1. Login to the console and select the application.
2. Click the Users icon in the vertical icon menu on the left.
3. Click User Registration.

The Registration toggle turns the registration API on or off. When the registration is turned off and a user attempts to register, the system returns error 3009.

registration-togglev4.zoom70

Email Confirmations

Backendless can send out an email requesting new registered users to confirm their email address. This feature can be configured in the Backendless Console:

1. Log into the console and select the application.
2. Click the Users icon in the vertical icon menu on the left.
3. Click User Registration.

email-confirmationv4.zoom70

When email confirmations are turned on (the feature is disabled by default), the "email" user property is required and must contain a value formatted as an email address. To configure the text of the email message, select Email Templates from the Users menu in the console and select the User made registration event.

Login

Registered users can login to establish their identity with the application using the API below. The login operation requires two properties: one marked as user identity and the second is password. Backendless automatically assigns the "AuthenticatedUser" role to all successfully logged in users. The role can be used to differentiate access to various resources (persistent objects, messaging channels, files) between authenticated users and guests.

Non-blocking Method:

Backendless.UserService.login( login, password, stayLoggedIn )
 .then( function( loggedInUser ) {
   })
 .catch( function( error ) {
   });

where:

login - a value for the property marked as identity.
password - user's password
stayLoggedIn - a boolean value requesting user login information to be saved so it can be reused when the application restarts (or page is reloaded).
loggedInUser - an instance of Backendless.User representing the logged in user.
error - error object for an error thrown either out of the then block or from the server.

Blocking Method:

var loggedInUser = Backendless.UserService.loginSync( login, password, stayLoggedIn );

where:

login - a value for the property marked as identity.
password - user's password
stayLoggedIn - a boolean value requesting user login information to be saved so it can be reused when the application restarts (or page is reloaded).
loggedInUser - an instance of Backendless.User representing the logged in user.

 

If the stayLoggedIn argument is set to true, the following APIs can be used to retrieved persisted information about the user:

// get objectId of the logged-in user:
var userObjectId = Backendless.LocalCache.get("current-user-id")

// get user-token of the logged-in user:
var userToken = Backendless.LocalCache.get("user-token")

// get current user object:
var userObject = Backendless.UserService.getCurrentUser()

Errors:

The following errors may occur during the Login API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error.

Error Code

Description

2002

Version is disabled or provided wrong application info (application id or secret key)

3000

Login has been disabled for the user account.

3001

Missing login settings, possibly invalid application id or version.

3002

User cannot login because Multiple Logins disabled and there is a logged in user for the account.

3003

Invalid login or password.

3006

Either login or password is an empty string value.

3034

User logins are disabled for the version of the application.

3036

Account locked out due to too many failed logins.

3038

One of the required parameters (application id, version, login or password) is null

3044

Multiple login limit for the same user account has been reached.

8000

Property value exceeds the length limit

 

Non-blocking Method Sample:

// do not forget to call Backendless.initApp( appId, apiKey ); to initialize your app

function userLoggedIn( user )
{
  console.log( "user has logged in" );
}

function gotError( err ) // see more on error handling
{
  console.log( "error message - " + err.message );
  console.log( "error code - " + err.statusCode );
}

Backendless.UserService.login( login, password, true )
 .then( userLoggedIn )
 .catch( gotError );

Blocking Method Sample:

// do not forget to call Backendless.initApp( appId, apiKey ) to initialize your app

var user;

try
{
  user = Backendless.UserService.loginSync( username, password );
}
catch( err ) // see more on error handling
{
  console.log( "error message - " + err.message );
  console.log( "error code - " + err.statusCode );
}

Validating User Login

The login operation provides a way to persist the user-token on the client side so it can be used when the application is restarted. This helps in streamlining the user experience since the user of the application does not need to login again. However, when the application restarts, it needs to check if the underlying user token, and hence the user session are still valid. This can be accomplished with the API below:

Methods:

Non-blocking method

function success( result )
{ 
  console.log( "Is login valid?: " + result );
} 

function error( err ) 
{ 
  console.log( err.message );
  console.log( err.statusCode );
} 

Backendless.UserService.isValidLogin()
 .then( success )
 .catch( error );

Blocking method

var userTokenIsValid = Backendless.UserService.isValidLoginSync(); 
console.log( "Is login valid?: " +  userTokenIsValid );

Return value:

The server returns a boolean value of true if token is valid, false otherwise.

 

Login with Facebook

Backendless integrates with Facebook to support user authentication and login into a Backendless application with a Facebook account. Using the integration application developer can provide a way to skip the application registration step and allow users to use their Facebook identity to register with and experience the application.

 

In order to integrate Facebook login with Backendless, client application must use the Facebook SDK to authenticate the user and then makes a Backendless API call to link the Facebook identity to a BackendlessUser instance.

 

Property Mapping

Backendless User objects may have their own set of user properties. As a result, when a user logs in using their Facebook account, their Facebook Graph API user fields are mapped to the Backendless user properties via "property mapping". There may be multiple mapping, each must reference  a Facebook user field and a name of a Backendless property. Once a user is authenticated, Backendless obtains the field values from the Facebook user graph object and populates the mapped properties in the BackendlessUser object. For the very first login, Backendless also creates the user in its internal system.

Backendless Configuration

A Backendless application (the server side) must be configured with Facebook's application App ID and secret key. See the "Social Settings" chapter of the user guide (the Manage section) for detailed instructions for configuring Backendless with Facebook.

Integration API

The API provides a way to "exchange" Facebook access token for Backendless user object which represents the corresponding Facebook user. Before using the API, application must perform login to Facebook using its SDK. The integration process consists of the following steps:

 

Configure the environment with the Facebook SDK. Complete instructions are available at: https://developers.facebook.com/docs/javascript/quickstart/v2.2
Configure Backendless backend with the Facebook App ID/API Key.
Use the API call described below:
Backendless.UserService.loginWithFacebookSdk( facebookFieldsMapping,
                                              stayLoggedIn,
                                              options ) 
 .then( function( result ) {
  })
 .catch( function( error ) {
  }); 

where:

facebookFieldsMapping - a mapping between the Facebook fields and Backendless user properties. Keys must be the names of the Facebook fields, values - the names of the Backendless properties.
stayLoggedIn - Boolean value, which should be set to true if a user must remain logged in after leaving the application. This argument is optional.

 

Login with Twitter

Twitter Login

To enable Twitter account log in to your Backendless application:

1. Create your application at https://apps.twitter.com/.
2. In the application's Settings tab, set the Website and Callback URL to https://api.backendless.com
3. From the Keys and Access Tokens tab, copy the Consumer Key and the Consumer Secret values.
4. Click the Permissions tab and select the Request email addresses from users checkbox. Click Update Settings.
5. Log in to your account in the Backendless Console, select your app and click Manage.
6. Scroll down to the Social Settings section and insert the values copied on step 4 into the Twitter Consumer key and Twitter Consumer secret fields.

Add the fields mapping with Twitter to allow the users to log in to your application with their Twitter account:

Backendless.UserService.loginWithTwitter( twitterFieldsMapping, stayLoggedIn )
  .then( function( result ) {
  })
  .catch( function( error ) {
  });

where:

twitterFieldsMapping - a mapping between the Twitter fields and Backendless user properties. Keys must be the names of the Twitter fields, values - the names of the Backendless properties.

stayLoggedIn - a boolean value requesting user login information to be saved on the client side, so it can be reused when the application restarts (or page is reloaded).

 

Login wih Google

This guide describes the process of configuring your Backendless backend to enable the Google account sign-in in your application.

 

Adding Google+ API and generating client ID

1. Open https://console.developers.google.com in a browser.
2. Choose an existing project or create a new one.
google-new-project
3. In your app dashboard click the Enable API button:
google-dashboard-enable-api
 
4. Use the Library page to find the Google+ API. Click Google+ API and enable it for your project.
google-api
 
 
google-enable-api
 
5. Open the Credentials page:
google-create-credentials
6. Complete the form by following the steps below:
 
a. Select Web browser (Javascript) for the app type and select User data. Click What credentials do I need?:
google-type-of-credentials
 
b. Set Authorized redirect URIs as https://api.backendless.com/<app-id>/<js-api-key>/users/social/googleplus/authorize. Don’t forget to substistute <app-id> and <js-api-key> with the values for your app, then click Create client ID:
google-authorized-redirect-uris
 
c. Enter your product name and click Continue:
google-consent-screen
 
d. Click Done.
google-add-credentials-to-your-project
 
7. Open the created credentials and copy Client ID and Client secret:
google-app-credentials
8. Open Backendless console in another browser tab. Select your app and navigate to the Manage > App Settings > Social Settings section. Paste the Client ID and Client secret from the previous step into the corresponding fields:
backendless-settings-google.zoom70

 

Google Login API

The API provides a way to "exchange" Google access token for a Backendless user object which represents the corresponding Google user.

Backendless.UserService.loginWithGooglePlus( 
              fieldsMapping,
              permissions,
              container,
              stayLoggedIn )
 .then( function( result ) {
  })
 .catch( function( error ) {
  });

where:

fieldsMapping - a mapping between the Google+ fields and Backendless user properties. Keys must be the names of the Google+ fields, values - the names of the Backendless properties. This argument is optional.
permissions - This attribute is deprecated and will be removed.
container - a reference to a DOM element that will be used for displaying an iframe element to open Google+ login popup. This argument is optional.
stayLoggedIn - a boolean value, which should be set to true if a user must remain logged in after leaving the application. This argument is optional.

 

User Properties

When a user registers with an application, he provides information which establishes user's identity. For one application these properties may include name, phone number and email address, for another it could be user id, age and gender. Backendless User Service allows each application to have a custom set of properties associated with the user entity.

Defining properties with the API

User properties must be defined in the object representing a user. When user registers or user object is updated, Backendless saves the property values in the Users table of Backendless Data service. If a property is not declared on the server-side, Backendless automatically creates a definition for it. This behavior can be turned off/on using the Dynamic Schema Definition configuration setting in the console (select Data > Configuration). The setting is turned on by default:

dynamic-schema-definition.zoom70

 

Set/Update User Property

When setting or updating a user property, the values must be of "primitive" data types (boolean, string, number, date). To assign a non-primitive value, use the Data relation API.

The following API can be used to set or update properties in a user object:

User properties can be set on an instance of the Backendless.User class. Setting a user property works the same as assigning any property of a JavaScript object. For example, the code below assigns a custom property phoneNumber:

var user = new Backendless.User();
user[ "phoneNumber" ] = "555-1212" 

Retrieve User Property Values

User object properties can be retrieved using the same approach as with any JavaScript object. For example, the code below retrieves a a value for the phoneNumber property:

var phoneNumberValue = user[ "phoneNumber" ]; 

Example:

The example below describes how to retrieve the phoneNumber user property.

  Backendless.UserService.login( "spidey@backendless.com", "greeng0blin" )
   .then( function( loggedInUser ) {
      console.log( "User has been logged in: " + loggedInUser.email );

     var phoneNumber = loggedInUser[ "phoneNumber" ];
     console.log( "User phone number: + phoneNumber );
   });

Defining properties with Console

User property management is available in the schema editor of the Users table. To open the schema editor:

1. Login to Backendless Console and select your application.
2. Click the Data icon, and select the Users table.
3. Click the Schema menu.

There are built-in user properties, such as email, name and password. The email and name properties can be deleted if they do not apply to your app, although it is recommended to keep the email property, since it is used for other purposes (for instance, password recovery). The password property cannot be deleted.

Identity Property

It is required that one of the properties is marked as identity. This is the property Backendless uses for the Login and Restore Password operations. As users register, Backendless ensures the value for the identity property is unique  in the context of a specific version of the application.

Password Property

"password" is a special property. Backendless automatically adds the property when an application is created. The following rules apply to the password property:

Password cannot be removed from the application.
Password cannot be used as identity.
Password is always a required property

users-schema.zoom80

To add a new property, click the New button.

Update User Object

User property values of the logged in users can be updated with an API operation described below. This operation is useful if an application needs to provide to the users the functionality for updating user profiles and registration properties. A user must be logged in in order to update his registration properties.

 

Non-blocking Method Signature:

Backendless.UserService.update( user )
 .then( function( updatedUser ) {
  })
 .catch( function( error ) {
  });

where

user - an instance of the Backendless.User class which contains property values to be updated for the account.
updatedUser - updated instance of Backendless.User.
error - either an error from the then block or an error returned by the server.

Blocking Method Signature:

var updatedUser = Backendless.UserService.updateSync( user );

where

user - an instance of the Backendless.User class which contains property values to be updated for the account.
updatedUser - updated instance of Backendless.User.

Errors:

The following errors may occur during the Update User Properties API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error.

Error Code

Description

2002

Version is disabled or provided wrong application info (application id or secret key)

3018

The property marked as "identity" is being updated and another user already has the specified value which must be unique.

3024

General "update registration" error. Error message should contain additional details.

3028

User is not logged in.

3029

Cannot modify properties of another user. Returned when one user is logged and the call attempts to modify properties of another user.

3030

Unable to locate user account - invalid user id.

3031

A new "dynamic" property is being added, but dynamic property definition is disabled.

3045

Required properties in the provided object do not contain values.

// do not forget to call Backendless.initApp( appId, jsAPIKey ) in the app initialization code

function updateUser(user) {
  user.address = "123 Main St";
  
  return Backendless.UserService.update( user );
}

function userUpdated( user ) {
  console.log( "user has been updated" );
}

function gotError( err ) {
  console.log( "error message - " + err.message );
  console.log( "error code - " + err.statusCode );
}
 
Backendless.UserService.login( login, password )
 .then( updateUser )
 .then( userUpdated )
 .catch( gotError ) );

Get Current User

A JavaScript application can retrieve an instance of Backendless.User representing the currently logged in user using the following API call:

Non-blocking method:

Backendless.UserService.getCurrentUser()
 .then( function( currentUser ) {
  })
 .catch( function ( error ) {
  });

Blocking method:

var currentUser = Backendless.UserService.getCurrentUserSync();

If a user is not logged in, the method returns null.

Logout

The Logout operation terminates user session and disassociates the AuthenticatedUser role from the subsequent requests made by the client application.

 

Non-blocking Method Signature:

Backendless.UserService.logout()
 .then( function() {
  })
 .catch( function( error ) {
  });

Blocking Method Signature:

Backendless.UserService.logoutSync()

Errors:

The following errors may occur during the Logout API call.See the Error Handling section for details on how to retrieve the error code when the server returns an error.

Error Code

Description

2002

Version is disabled or provided wrong application info (application id or secret key)

3007

Invalid application-id or version.

3023

General error while executing logout. Error details should be available in the message property.

Example:

Non-blocking Method Example:

// do not forget to call Backendless.initApp( appId, jsAPIKey ) in the app initialization code

function userLoggedOut()
{
  console.log( "user has been logged out" );
}

function gotError( err ) // see more on error handling
{
  console.log( "error message - " + err.message );
  console.log( "error code - " + err.statusCode );
}

function logoutUser()
{
  Backendless.UserService.logout()
   .then( userLoggedOut )
   .catch( gotError ) );
}

Blocking Method Example:

// do not forget to call Backendless.initApp( appId, jsAPIKey ) in the app initialization code

try
{
  Backendless.UserService.logoutSync();
}
catch( err ) // see more on error handling
{
  // logout failed, to get the error code, call err.statusCode
  console.log( "error message - " + err.message );
  console.log( "error code - " + err.statusCode );
}

Password Reset

Backendless supports two kinds of password reset:

1. Reset to system (backend) generated password.
2. Reset to password assigned by the user.

With the first option, an email is sent to the user with a regenerated password assigned to the user account. With the second option, an email is sent with a link to a webpage which contains a form where the user can enter the new password.

You can choose the type of reset for your application using Backendless Console. To change the password reset approach navigate to Users > Email Templates and select either User requests password recovery or User requests password recovery by link. Only one of these options can be active at a time. To make an option active, make sure the Do not send email for the event checkbox is not selected. If you make a change, make sure to click the SAVE button at the bottom of the screen.

 

For example, the following screenshot shows a configuration where the system will send out a link so the user can assign his own password. Notice the red color for the User requests password recovery option - this indicated that the option is disabled for the app:

active-mail-template.zoom80

 

In both cases (system-assigned or user-assigned passwords), the password reset sequence must initiated through the API. When the system assigns a password, it is still possible to update the user password using the Data Service API.

 

For the "password change via a link" option Backendless provides a default HTML form the link in the email points to. The HTML page is located in the File storage of the application where it can be edited and customized for branding purposes. The files are located in the /web/templates/change_password directory:

password-change-template.zoom80

Non-blocking Method Signature:

   
Backendless.UserService.restorePassword( identityValue )
 .then( function() {
  })
 .catch( function( error ) {
  }); 

where

identityValue - value for a property marked as identity.

Blocking Method Signature:

   
Backendless.UserService.restorePasswordSync( identityValue )

where

identityValue - value for a property marked as identity.

Errors:

The following errors may occur during the Password Recovery API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error.

Error Code

Description

2002

Version is disabled or provided wrong application info (application id or secret key)

3020

Unable to find user with the specified login (invalid user identity).

3025

General password recovery error. Additional details should be available in the "message" property of the response.

3038

One of the requirement arguments (application id, version or user identity) is missing.

 

Non-blocking Call Example:

   
// do not forget to call Backendless.initApp( appId, jsAPIKey ) in the app initialization code
function passwordRecoverySent()
{
  console.log( "an email with a link to restore password has been sent to the user" );
}

function gotError( err ) // see more on error handling
{
  console.log( "error message - " + err.message );
  console.log( "error code - " + err.statusCode );
}

Backendless.UserService.restorePassword( login )
 .then( passwordRecoverySent )
 .catch( gotError );

Blocking Call Example:

   
// do not forget to call Backendless.initApp( appId, jsAPIKey ) in the app initialization code

try
{
  var login = // user's identity value
  Backendless.UserService.restorePasswordSync( login )
}
catch( err )  // see more on error handling
{
  console.log( "error message - " + err.message );
  console.log( "error code - " + err.statusCode );
}

 

User to Geo Relations

Backendless Geo Service manages application's geo location data and provides APIs to work with geopoints. Backendless supports integration between user objects managed by User Service and geopoints for the scenarios when a logical connection between the two entity types must exist in an application.

 

Relations between a user object and a geopoint are built the same way as between a data object and a geo point. The User-to-Geo integration is implemented through object relations. The Users table schema may declare a table column with a special data type - "GeoPoint Relationship". As a result, the user objects in the table may contain a reference to one or more geopoints. Backendless provides an API to establish a relationship between a user object and geopoints. Additionally, when a user object is retrieved by using the data retrieval API, any related geopoints can be retrieved by using the same mechanisms used for loading data relations. The user-to-geo relation is bidirectional, it means a geopoint may reference a user object in its metadata. You can learn more about it in the Relations with Data Objects section of the Geolocation service documentation.

 

A relation between a user object and a geopoint (or a collection of) can be established by using either the "code first" or the "schema first" approaches. With the former, the relationship is established through an API call. If the relationship is not declared in the schema, Backendless creates it based on the information in the API. With the "schema first" approach, application developer can declare a user-to-geo property first. In either one of these approaches, once a relationship is declared, user objects and geopoints may be linked together by using the Backendless console as well. For information on how to declare Users to Geo relation columns and how to link User objects with GeoPoints using Backendless console, see the Relations with Geo Objects of the Data Service documentation.

 

To create a relation both the user object and geopoint(s) must already exist in the Backendless database. It is not possible to create a relation with an object which has not been previously saved.

Creating User-to-Geo Relations with the API

Creating a relationship between a user object and a geopoint (or a collection of) uses the same API as setting/adding related objects to a data object. In the case of the user-to-geo relations, the related entity is a geopoint or a collection of geopoints.

The example below demonstrates how to assign coordinates to a user's location, that is link a user object with one or several geopoints.

One-to-one Relation

Non-blocking API sample:

Backendless.Data.of(Backendless.User).findFirst()
 .then( function( firstUser ){ 
    var samplePoint = new Backendless.GeoPoint(); 
    samplePoint.latitude = 48.85; 
    samplePoint.longitude = 2.35; 
    firstUser.location = samplePoint; 
    
    Backendless.UserService.update(firstUser)
     .then( function( updatedUser ) {
       console.log( "user has been updated" );
     }) 
     .catch( function( error ) {
       console.log( "error " + error.message );
     })
  })          
 .catch( function( error ) {
   console.log( "error " + error.message );
 }); 

Blocking API sample:

var firstUser = Backendless.Persistence.of(Backendless.User).findFirstSync();

var samplePoint = new Backendless.GeoPoint(); 
samplePoint.latitude = 48.85; 
samplePoint.longitude = 2.35; 
firstUser.location = samplePoint; 
    
Backendless.UserService.updateSync(firstUser);
console.log( "user has been updated" );

Backendless Console can be used to verify the new relation. The geopoint will show up as a property in the location column of the user object.

One-to-many Relation

Non-blocking API sample:

Backendless.Data.of(Backendless.User).findFirst()
 .then( function( firstUser ){ 
    var samplePoint1 = new Backendless.GeoPoint(); 
    samplePoint1.latitude = 48.85; 
    samplePoint1.longitude = 2.35; 
    
    var samplePoint2 = new Backendless.GeoPoint();
    samplePoint2.latitude = 40.40; 
    samplePoint2.longitude = 3.68; 
    firstUser.locations = [ samplePoint1, samplePoint2 ]; 
    
    Backendless.UserService.update(firstUser)
     .then( function( updatedUser ) {
       console.log( "user has been updated" );
     }) 
     .catch( function( error ) {
       console.log( "error " + error.message );
     })
  })          
 .catch( function( error ) {
   console.log( "error " + error.message );
 }); 

Blocking API sample:

var firstUser = Backendless.Data.of(Backendless.User).findFirstSync(); 
var samplePoint = new Backendless.GeoPoint(); 
samplePoint.latitude = 48.85; 
samplePoint.longitude = 2.35; 

var samplePoint2 = new Backendless.GeoPoint(); 
samplePoint2.latitude = 40.40; 
samplePoint2.longitude = 3.68; 
firstUser.locations = [samplePoint, samplePoint2]; 
Backendless.UserService.updateSync( firstUser ); 

Backendless Console can be used to verify the new relation. The geopoint will show up as a property in the locations column of the user object.

Security and User Roles

Application's user accounts can be mapped to roles using the API documented below. A Backendless backend contains some system roles and may include developer-defined roles. There several system roles available in all Backendless backends. These roles are automatically assigned to every user under various conditions. Each role can configured with global, container and asset permissions.

 

Core security roles are assigned based on user authentication status and whether the user logged in via "classic" backendless login or through a social network. These roles are:

Security Role

Assigned to user when an API request is made by:

NotAuthenticatedUser

...a user who is not logged in.

AuthenticatedUser

...a user who is logged in.

SocialUser

...a user who is logged in via a social network (Facebook, Twitter, Google)

FacebookUser

...a user who is logged in via Facebook

GooglePlusUser

...a user who is logged in via Google

TwitterUser

...a user who is logged in via Twitter

 

Additionally the following roles are automatically assigned by Backendless to an API request when a corresponding secret API key is used. By configuring access permissions to these roles an application may allow to custom server-side code (ServerCodeUser role), but completely reject access to the client apps (all other roles).

Security Role

Role is assigned when API uses secret key:

ASUser

ActionScript secret key

AndroidUser

Android secret key

DotNetUser

.NET secret key

IOSUser

iOS secret key

JSUser

JavaScript secret key

RestUser

REST secret key

ServerCodeUser

CodeRunner secret key

 

Backendless assigns multiple security roles for the same API request. For example, an API request made from a JavaScript application where user is not logged will have the following roles assigned: NotAuthenticatedUser and JSUser.

 

When an API call originates from business logic (Java, JS or Codeless), Backendless assigns the ServerCodeUser role.

Business logic is the only exception to the rule for assigning NotAuthenticatedUser and AuthenticatedUser roles. When business logic makes an API call and there is no authenticated user in the context of the call, Backendless assigns only the ServerCodeUser role. Otherwise, if there is an authenticated user, then both ServerCodeUser and AuthenticatedUser roles are assigned to the request.

 

Adding Roles

A developer-defined role can be added using Backendless Console:

1. Login to Console and select an application.
2. Click the Users icon.
3. Click the Security and Permissions section.
4. Click the Add Role button and enter a name for the role.
5. Click OK to save the role.

Role Permissions

A role may have multiple permissions assigned to it. A permission may either allow (GRANT) or reject (DENY) an API operation. Every operation which can be restricted with a permission has a subject. For example, the subject of a Data Service operation is a data record. For the File Service operations, the subject is a file.

All permissions are organized into a hierarchy: The global permissions apply to everything in the system. They either allow (GRANT) or reject (DENY) an API operation without distinguishing the subject. For example, a global permission may reject the Data Service's find operations for the users in the NotAuthenticatedRole. As a result, any not-authenticated user will not be able to retrieve an object from any data table in the application.

The asset container permissions inherit from the global ones and apply to a specific asset container, such as a data table. These permissions are scoped to a specific container type. For example, you may reject the get operation on file directory /privatefiles/ for the users in role X. As a result, any user who belongs to the role X will not be able to retrieve files from that directory.

Finally, the asset permissions inherit from the ones for the corresponding asset container (if they are available) or from the global set. For example, an asset permission may reject access to a specific data object for one role and grant access to the same object for another role.

The diagram below illustrates the permissions inheritance ladder:

permission-inheritance

Once a role is assigned to a user account, any permissions assigned to the role (either granting or denying an API operation) automatically apply to the operations executed by the application on behalf of the user.

Retrieving Available User Roles

This operation returns a list of the roles associated with the user account currently logged in to the Backendless application. If the request is sent without logging in, the system will return only one role - NotAuthenticatedUser.

Non-blocking Method:

Backendless.UserService.getUserRoles()
 .then( function( arrayOfRoles ) {
  })
 .catch( function( error ) {
  });

Blocking Method:

var arrayOfRoles = Backendless.UserService.getUserRolesSync()

Example:

Before retrieving the user roles, log in as described in the Login section. Then, retrieve the user roles as follows:

Backendless.UserService.login("foo@backendless.com", "password", true )
 .then( function( loggedInUser ) {
   Backendless.UserService.getUserRoles()
    .then( function( arrayOfUserRoles ) {
      for( var index in arrayOfUserRoles )
        console.log( "user belongs to role: " + arrayOfUserRoles[ index ] ); 
    })
    .catch( function( error ) {
      console.log( "error " + error.message );
    });
  })
 .catch( function( error ) {
   console.log( "error " + error.message );
 });

Assigning a Role to a User:

This operation is available only from Custom Business Logic. Applications must use the "Code Runner API key" in order for the API call to be  accepted on the server. The reason for this restriction is that a malicious use of this API can easily compromise application's security. As a result, this API must be used from a controlled environment.

Non-blocking Method:

Backendless.UserService.assignRole( identity, roleName )
 .then( function() {
  })
 .catch( function( error ) {
 }); 

where:

identity - value for a user property marked as identity.
roleName - the name of the role to assign to the user.

Blocking Method:

Backendless.UserService.assignRoleSync( identity, roleName );

where:

identity - user identification. A value for the property marked as identity.
roleName - name of the role to assign to the user account.

Unassigning a Role from a User:

This operation is available only from Custom Business Logic. Applications must use the "Code Runner API key" in order for the API call to be  accepted on the server. The reason for this restriction is that a malicious use of this API can easily compromise application's security. As a result, this API must be used from a controlled environment.

Non-blocking Method:

Backendless.UserService.unassignRole( identity, roleName )
 .then( function() {
  })
 .catch( function( error ) {
  });

where:

identity - user identification. A value for the user property marked as identity.
roleName - name of the role to remove from the user account.

Blocking Method:

Backendless.UserService.unassignRoleSync( identity, roleName );

where:

identity - user identity. A value for the user property marked as identity.
roleName - name of the role to remove from the user account.

Errors:

The following errors may occur during the API calls described above. See the Error Handling section for details on how to retrieve the error code when the server returns an error.

Error Code

Description

2002

Version is disabled or provided wrong application info (application id or secret key)

2005

Could not find role.

3038

One of the required parameters (user identity or roleName) is null.

3057

Could not find user by id or identity.

3058

Could not assign role to user.

3059

Could not unassign role to user.

Global Permissions

The global service permissions apply to all operations of a Backendless service. For example, global Data Service permissions assigned to a role apply to all API operations without distinguishing the subject of an operation. (A subject is a resource targeted by an API operation - for example a data object in a "save" call).

Global permissions can be viewed and managed in Backendless Console.  After selecting an application, click Users, then Security Roles:

user-rolesv4.zoom70

 

Every Backendless service is represented by a column. Each cell in a column is a group of API operations. For example "Find" under "Data" represents all Find operations of the Data Service. The check mark in a cell indicates that that the operation is globally permitted. Each global permission has two states:

Grant - represented by a green check mark - grants the rights to execute an operation for the users in the selected role.
Deny - represented by a red X - denies the rights to execute an operation.

Asset Container Permissions

Asset Container permissions apply to all subjects within a specific container. Supported containers may be:

Data tables                - a permission assigned at that level applies to all operations on the table. For example, storing, deleting or updating objects in the table and running queries.
Messaging channels        - a permission assigned to a channel applies to all operations on the channel - publishing a message, subscribing to receive messages, etc.
File directories        - a permission assigned to a file directory applies to all file-related operations in the directory - file upload, fetching a file, etc.
Geo categories        - a permission assigned to a geo category applies to all geo point-related operation in the category - adding, deleting or updating a geo point
Media tubes        - a permission assigned to a media tube applies to all operation on media streams in the tube.

To view, assign or modify an asset container permission, use a corresponding screen in the Backendless Console. For example, to restrict access to a data table, switch to the Data view, select a table and click the Permissions menu. The user interface has two views - one is for managing user permissions and the other for roles. To modify permissions for a user account:

1. Click the User Permissions menu.
2. Enter the user name in the search field to locate the user account..
3. Click an icon representing the permission state to modify the permission.

user-permissionsv4.zoom70

Similarly permissions can be assigned or modified for specific roles - use the Role Permissions menu.

Asset Permissions

Asset permissions are the most granular in the Backendless security system as they apply to a specific object. Currently this type of permissions is available only for data objects stored in Backendless Data Service and files stored in Backendless File Service. Permissions assigned to an asset (data objects or files) supersede all other permissions - including global and asset container ones. Similar to the asset container permissions, asset permissions may apply to specific user accounts or roles. This approach becomes particularly useful when different objects/files in the same container (table or directory) should have different access levels. For example, suppose a data table called Orders contains purchase orders entered by different sales people. Every sales person should be able to see only their purchase orders, but the sales manager should see the purchase orders of his subordinates. This can be accomplished by using an asset permission scheme.

Data Service Asset Permissions

Permissions for individual data objects can be managed either with Backendless Console or using the API. To manage object's permissions with console:

1. Login to console and select an application.
2. Click Data, then select the table containing the object for which permissions should be managed.
3. There is a special "lock" icon in the ACL column for every object in the table.
acl-iconv4.zoom80
4. Click the lock icon to manage object's permissions.
5. The permissions user interface includes two menu items: Users Permissions and Roles Permissions. The former is responsible for assigning permissions for the selected object to specific users. The latter does the same but for application roles.

The screenshot below explains various parts of the user interface:

roles-permissions-v4.zoom80

The intersection of each column and a role/user either grants or denies access to the operations represented by the column. The intersections may contain the following visualizations:

ico-checkmark-disable (gray check mark)        - INHERIT. This state represents the permissions for the user/role and the operation are inherited from either asset container or global permissions.
ico-checkmark2 (green check mark) - GRANT. This state represents that the permission is granted for the user/role to perform the corresponding operation.
ico-error (red cross)                - DENY. This state represents that the permission to execute the corresponding operation is denied to the user/role.

 

Data Service API

Overview

The Backendless Data Service is a highly scalable object storage system. The system has the qualities of both a traditional structured/relational (SQL) storage and an unstructured NoSQL database. Objects are stored in tables, which have columns with assigned data types. A table can be created dynamically, when an object is being persisted and there is no table corresponding to the object's type. Similarly, when an object is saved or updated  and contains properties which do not have corresponding columns, Backendless dynamically creates the columns in the table.

 

There are two formats for representing data objects: using custom classes or using dictionaries. With the custom class approach all properties of a class are mapped to data table columns. Instances of class are saved in a data table with the same name as the name of the class. When using dictionaries, objects are structured as key/value pairs where keys identify column names and values their respective values. See the Data Object section for additional details.

 

Data Service supports table relations which means a table may declare a column referencing another table. At the object level it is implemented through an object property which contains a reference to another object (one to one relation) or a collection of objects (one to many relations). When an object is retrieved from the server, its related objects can be retrieved in the same request (single-step retrieval) or later on through a separate request (two-step retrieval).

 

Object search in Backendless can be done using the SQL syntax. A search request is sent to a specific table and a search query identifies a condition the found object must match. As a result, the query must be the format of what goes into the WHERE clause of a traditional SQL query. For more details see the Search with the Where clause section of the guide.

Data Object

Backendless database stores objects in data tables. A persisted object is stored in a language-independent format. Backendless supports persistence of arbitrary (typed or untyped) JavaScript objects. There are a few requirements imposed on the structure of the persisted objects:

Class must declare at least one property.
Backendless automatically assigns a unique ID to every persisted object. If the application needs to have access to the assigned ID, it can obtain it using a dynamically assigned property. Do NOT declare the objectId property - it will change the persistence behavior when objects are created in a remote data store:

var objectId = persistedDataObject["objectId"];

In addition to objectId, Backendless maintains two additional properties for every persisted object - created and updated. The former contains the timestamp when the object was initially created in the Backendless data store. The latter is updated every time the object is updated. To get access to these values use the dynamically assigned properties:

var createdTime = persistedDataObject["created"];
var updatedTime = persistedDataObject["updated"];

 

Saving Data Objects

The API to save an object can be used for two separate scenarios: (1) creating new objects in the database and (2) updating existing objects.

If an object has been previously saved, it is updated in the database, otherwise it is saved (created). The save operation checks if the object has objectId assigned by the server. In that case, the object is updated, otherwise it is created in the Backendless data store. In case when there is no table for the persisted object, Backendless creates one and maps table's columns to the object's properties.

The objectId property is automatically assigned to all objects in the database when they are initially saved. See the Data Object section for details on objectId.

Blocking API:

Backendless.Data.of( "TABLE-NAME" ).saveSync( { propertyA:"", propertyB:""} );
function DataTypeX() {
  this.propertyA = "";
  this.propertyB = "";
}
Backendless.Data.of( DataTypeX ).saveSync( new DataTypeX() );

Non-blocking API:

Backendless.Data.of( "TABLE-NAME" ).save( { propertyA:"", propertyB:""} )
 .then( function( savedObject ) {
  })
 .catch( function( error ) {
  });
function DataTypeX() {
  this.propertyA = "";
  this.propertyB = "";
}
Backendless.Data.of( DataTypeX ).save( new DataTypeX() )
 .then( function( savedObject ) {
  })
 .catch( function( error ) {
  });

where:

DataTypeX - function/class identifying the table where the object will be stored. Instances of the class will be saved in the table.
"TABLE-NAME" - name of the table where the object will be stored.

Return Value:

Returns the saved object with the objectId property assigned by Backendless

Example:

var contact = {
    name:"Jack Daniels",
    age:147,
    phone:"777-777-777",
    title:"Favorites"
}
  
// blocking API:
var savedContact = Backendless.Data.of( "Contact" ).saveSync( contact );

// non-blocking API
Backendless.Data.of( "Contact" ).save( contact )
  .then( function( savedObject ) {
      console.log( "new Contact instance has been saved" );
    })
  .catch( function( error ) {
      console.log( "an error has occurred " + error.message );
    });
Consider the following class:
function Contact(args) {
    args = args || {};
    this.name = args.name || "";
    this.age = args.age || "";
    this.phone = args.phone || "";
    this.title = args.title || "";
}
The following code saves a new instance of the Contact class:
// do not forget to initialize the app with Backendless.initApp( appId, jsAPIKey );

var contactObject = new Contact( {
    name: "James Bond",
    age: 45,
    phone: "1-800-JAMESBOND",
    title: "chief spying officer"
});

// blocking API
var savedContact = Backendless.Data.of( Contact ).saveSync( contactObject );

// non-blocking API
Backendless.Data.of( Contact ).save( contact )
  .then( function( savedObject ) {
      console.log( "new Contact instance has been saved" );
    })
  .catch( function( error ) {
      console.log( "an error has occurred " + error.message );
    });

 

Updating Data Objects

Updating a data object which has been previously stored in the Backendless data store can be done using the same API as for saving the object initially. Once an object has objectId assigned, the call to save the object will update it on the server.

Blocking API:

Backendless.Data.of( "TABLE-NAME" ).saveSync( { propertyA:"", propertyB:""} );
function DataTypeX() {
  this.propertyA = "";
  this.propertyB = "";
}
Backendless.Data.of( DataTypeX ).saveSync( new DataTypeX() );

Non-blocking API:

Backendless.Data.of( "TABLE-NAME" ).save( { propertyA:"", propertyB:""} )
 .then( function( savedObject ) {
  })
 .catch( function( error ) {
  });
function DataTypeX() {
  this.propertyA = "";
  this.propertyB = "";
}
Backendless.Data.of( DataTypeX ).save( new DataTypeX() )
 .then( function( savedObject ) {
  })
 .catch( function( error ) {
  });

where:

DataTypeX - function/class identifying the table where the object will be updated. Method argument must be an instance of the class.
"TABLE-NAME" - name of the table where the object will be updated.

 

Return Value:

Returns updated object.

Example:

// to update an existing object, it must have the "objectId"
// property with a value assigned by Backendless
var contact = {
    objectId:"XXXX-XXXX-XXXX-XXXXX",
    name:"Jack Daniels",
    age:147,
    phone:"777-777-777",
    title:"Favorites"
}
  
// blocking API:
var savedContact = Backendless.Data.of( "Contact" ).saveSync( contact );

// non-blocking API
Backendless.Data.of( "Contact" ).save( contact )
  .then( function( savedObject ) {
      console.log( "Contact instance has been updated" );
    })
  .catch( function( error ) {
      console.log( "an error has occurred " + error.message );
    });
Consider the following class:
function Contact(args) {
    args = args || {};
    this.name = args.name || "";
    this.age = args.age || "";
    this.phone = args.phone || "";
    this.title = args.title || "";
}
The following code updates a Contact object:
// do not forget to initialize the app with Backendless.initApp( appId, jsAPIKey );

// to update an existing object, it must have the "objectId"
// property with a value assigned by Backendless
var contactObject = new Contact( {
    objectId: "XXXXX-XXXXX-XXXXXX-XXXXX",
    name: "James Bond",
    age: 45,
    phone: "1-800-JAMESBOND",
    title: "chief spying officer"
});

// blocking API
var savedContact = Backendless.Data.of( Contact ).saveSync( contactObject );

// non-blocking API
Backendless.Data.of( Contact ).save( contact )
  .then( function( savedObject ) {
      console.log( "Contact object has been updated" );
    })
  .catch( function( error ) {
      console.log( "an error has occurred " + error.message );
    });

 

Deleting Data Objects

The API completely removes an object from the persistent store. If the object is successfully delete, the API returns the timestamp of the exact deletion time in milliseconds.

Blocking API:

Backendless.Data.of( "TABLE-NAME" ).removeSync( { objectId:"XXXX-XXXX-XXXX" } );
function DataTypeX() {
  // define properties of the class here
}

Backendless.Data.of( DataTypeX ).removeSync( dataTypeXInstance );

Non-blocking API:

Backendless.Data.of( "TABLE-NAME" ).remove( { objectId:"XXXX-XXXX-XXXX-XXXX" } )
 .then( function( timestamp ) {
  })
 .catch( function( error ) {
  });
function DataTypeX() {
  // define properties of the class here
}
Backendless.Data.of( DataTypeX ).remove( dataTypeXInstance )
 .then( function( timestamp ) {
  })
 .catch( function( error ) {
  });

where:

DataTypeX - function/class identifying the table where the object will be deleted.
"TABLE-NAME" - name of the table where the object will be deleted.
dataTypeXInstance - object to delete. Must contain the objectId property assigned by Backendless.

remove/removeSync method argument must be an object with the objectId property containing a value assigned by Backendless. The object must be stored in the data table identified in the of() function.

Return Value:

Returns a time stamp when the server-side removed the object from the data store

Example:

// to delete an object on the server, it must have the "objectId"
// property with a value assigned by Backendless
var contact = {
    objectId:"XXXX-XXXX-XXXX-XXXXX",
    name:"Jack Daniels",
    age:147,
    phone:"777-777-777",
    title:"Favorites"
}
  
// blocking API:
Backendless.Data.of( "Contact" ).removeSync( contact );

// non-blocking API
Backendless.Data.of( "Contact" ).remove( contact )
  .then( function( timestamp ) {
      console.log( "Contact instance has been deleted" );
    })
  .catch( function( error ) {
      console.log( "an error has occurred " + error.message );
    });
Consider the following class:
function Contact(args) {
    args = args || {};
    this.name = args.name || "";
    this.age = args.age || "";
    this.phone = args.phone || "";
    this.title = args.title || "";
}
The following code deletes a Contact object:
// do not forget to initialize the app with Backendless.initApp( appId, jsAPIKey );

// to delete an object stored on the server, it must have the "objectId"
// property with a value assigned by Backendless
var contactObject = new Contact( {
    objectId: "XXXXX-XXXXX-XXXXXX-XXXXX",
    name: "James Bond",
    age: 45,
    phone: "1-800-JAMESBOND",
    title: "chief spying officer"
});

// blocking API
Backendless.Data.of( Contact ).removeSync( contactObject );

// non-blocking API
Backendless.Data.of( Contact ).remove( contact )
  .then( function( timestamp ) {
      console.log( "Contact object has been deleted" );
    })
  .catch( function( error ) {
      console.log( "an error has occurred " + error.message );
    });

 

Retrieving Schema Definition

Backendless provides API for data table schema introspection. The API returns information about table's columns and their data types, whether a value for a column is required or if there is a default value.

Non-blocking API:

Backendless.Data.describe( tableName )
 .then( function( schemaProps ) {
  })
 .catch( function( error ) {
  });

Blocking API:

var schemaProps = Backendless.Data.describeSync( tableName );

where:

tableName - name of a table for which to retrieve schema definition.

Return value:

Returns an array of object's properties. Each object in the array describes a column in the specified table using the structure below:

{
  autoLoad: true or false,
  customRegex: value,
  defaultValue: null or value,
  isPrimaryKey: true or false,
  name: value,
  relatedTable: null or table name,
  required: true or false,
  type: data type
}

where:

autoLoad - applies only to relations. If true, the property is set to auto-load related data for the data retrieval queries.
customRegex - a regular expression assigned to the column as a validator. The validator applies when a new object is saved in the table or an existing one is updated.
defaultValue - a default value assigned to any object saved/updated in the table where the column does not have a value.
isPrimaryKey - true if the column is or is a part of a primary key.
name - contains the name of a property.
relatedTable - contains the name of the related table(s).
required - defines whether a property is optional or required for the requests which save the initial object or update an existing one.
type - defines the property type.

Example:

Suppose the Person table has the following schema - there are two developer-defined columns, age and name.

person-schema.zoom70

The following code retrieves the schema definition for the Person table:

Backendless.Data.describe( "Person" )
 .then( function( schemaArray ) {
    for( var i in schemaArray )
    {
      console.log( "property name - " + schemaArray[ i ].name );
      console.log( "\tis property required - " + schemaArray[ i ].required );
      console.log( "\tproperty data type - " + schemaArray[ i ].type );
      console.log( "\tdefault value - " + schemaArray[ i ].defaultValue );
      console.log( "\tis property primary key - " + schemaArray[ i ].identity );
    }   
  })
 .catch( function( error ) {
 });

The code produces the following output:

property name - objectId
  is property required - false
  property data type - STRING_ID
  default value - null
  is property identity - undefined
property name - age
  is property required - false
  property data type - INT
  default value - null
  is property identity - undefined
property name - updated
  is property required - false
  property data type - DATETIME
  default value - null
  is property identity - undefined
property name - name
  is property required - false
  property data type - STRING
  default value - null
  is property identity - undefined
property name - created
  is property required - false
  property data type - DATETIME
  default value - null
  is property identity - undefined
property name - ownerId
  is property required - false
  property data type - STRING
  default value - null
  is property identity - undefined

 

Get Object Count

The Object Count API provides a way to obtain the following values from the server:

Number of objects in a table
Number of objects matching query
Number of related objects

 

Non-blocking API:

// Get total object count for the "TABLE-NAME" table
Backendless.Data.of( "TABLE-NAME" ).getObjectCount()
 .then( function( count ) {
  })
 .catch( function( error ) {
 });
 
// Get object count for the objects matching the 
// query from the "TABLE-NAME" table
Backendless.Data.of( "TABLE-NAME" ).getObjectCount( queryBuilder )
 .then( function( count ) {
  })
 .catch( function( error ) {
 });
// define constructor function. Instances of the DataTypeX class
// will be stored in the "DataTypeX" table
function DataTypeX() {
  // define class properties here
}

// Get total object count for the "DataTypeX" table
Backendless.Data.of( DataTypeX ).getObjectCount()
 .then( function( count ) {
  })
 .catch( function( error ) {
 });
 
// Get object count for the objects matching the query from 
// the "DataTypeX" table
Backendless.Data.of( DataTypeX ).getObjectCount( queryBuilder )
 .then( function( count ) {
  })
 .catch( function( error ) {
 });

Blocking API:

// get total object count for the TABLE-NAME table
var objectCount = Backendless.Data.of( "TABLE-NAME" ).getObjectCountSync();

// Get object count for the objects matching the query from the "TABLE-NAME" table
var objectCount = Backendless.Data.of( "TABLE-NAME" ).getObjectCountSync( queryBuilder );
// define constructor function. Instances of the DataTypeX class
// will be stored in the "DataTypeX" table
function DataTypeX() {
  // define class properties here
}

// Get total object count for the "DataTypeX" table
var objectCount = Backendless.Data.of( DataTypeX ).getObjectCountSync();

// Get object count for the objects matching the query from the "DataTypeX" table
var objectCount = Backendless.Data.of( DataTypeX ).getObjectCountSync( queryBuilder );

where:

DataTypeX - name of the table where object count should be calculated.
"TABLE-NAME" - name of the table where object count should be calculated.
queryBuilder - instance of Backendless.DataQueryBuilder. With the "object count" API, the class can used only to set the "whereClause" as shown below:
DataQueryBuilder.create().setWhereClause( whereClauseValue );

Return Value:

Returns the number of objects in the data table optionally matching the provided query.

Example:

Total object count for a table:

The following sample request retrieves total number of objects in table Order:

Backendless.Data.of( "Order" ).getObjectCount()
 .then( function( count ) {
   console.log( "total objects in the Order table - " + count );
  })
 .catch( function( error ) {
   console.log( "error - " + error.message );
  });
function Order {
  // define the properties of the Order class here
}

Backendless.Data.of( Order ).getObjectCount()
 .then( function( count ) {
   console.log( "total objects in the Order table - " + count );
  })
 .catch( function( error ) {
   console.log( "error - " + error.message );
  });

Object count for a query:

The following sample request retrieves total number of objects in table Order which satisfy the condition of orderAmount > 100:

var queryBuilder = DataQueryBuilder.create().setWhereClause( "orderAmount > 100" );

Backendless.Data.of( "Order" ).getObjectCount( queryBuilder )
 .then( function( count ) {
   console.log( "found objects matching query in the Order table - " + count );
  })
 .catch( function( error ) {
   console.log( "error - " + error.message );
  });
function Order {
  // properties of the Order class are defined here
}

var queryBuilder = DataQueryBuilder.create().setWhereClause( "orderAmount > 100" );

Backendless.Data.of( Order ).getObjectCount( queryBuilder )
 .then( function( count ) {
   console.log( "found objects matching query in the Order table - " + count );
  })
 .catch( function( error ) {
   console.log( "error - " + error.message );
  });

Related object count:

The following sample request retrieves total number of related "child" objects for a parent object. The parent table is Person. It contains a relation column called address pointing to the Address table. The query below retrieves a count of related child objects for a parent object with objectID of XXXX-XXXX-XXXX-XXXX. The whereClause query syntax for this scenario is:
 
Person[address].objectId = 'XXXX-XXXX-XXXX-XXXX'

 

Notice the API request is sent to Address (the child table):

var queryBuilder = DataQueryBuilder.create();
queryBuilder.setWhereClause( "Person[address].objectId = 'XXXX-XXXX-XXXX-XXXX'" );

Backendless.Data.of( "Address" ).getObjectCount( queryBuilder )
 .then( function( count ) {
   console.log( "found child objects for the parent - " + count );
  })
 .catch( function( error ) {
   console.log( "error - " + error.message );
  });
function Address {
  // properties of the Address class go here
}

var queryBuilder = DataQueryBuilder.create();
queryBuilder.setWhereClause( "Person[address].objectId = 'XXXX-XXXX-XXXX-XXXX'" );

Backendless.Data.of( Address ).getObjectCount( queryBuilder )
 .then( function( count ) {
   console.log( "found child objects for the parent " + count );
  })
 .catch( function( error ) {
   console.log( "error - " + error.message );
  });

 

Basic Object Retrieval

Backendless supports multiple basic search operations. These include finding an object by ID, finding first or last object in the collection or retrieving the entire persisted collection. Each method is available in both synchronous and asynchronous versions:

Retrieving Data Objects

Non-blocking APIs:

Retrieve data objects with the default paging setting from a table:
Backendless.Data.of( "TABLE-NAME" ).find()
 .then( function( result ) {
  })
 .catch( function( error ) {
  });
Find first data object from a table. The first data object is the first one saved in the data store:
Backendless.Data.of( "TABLE-NAME" ).findFirst()
 .then( function( result ) {
  })
 .catch( function( error ) {
  });
Find last data object from a table. The last data object is the last one saved in the data store:
Backendless.Data.of( "TABLE-NAME" ).findLast()
 .then( function( result ) {
  })
 .catch( function( error ) {
  });
Find a data object by its objectId:
Backendless.Data.of( "TABLE-NAME" ).findById( objectId )
 .then( function( result ) {
  })
 .catch( function( error ) {
  });
Define a function/class which will represent a data table on the server:
function X() {
  // properties of the class defined here
} 
Retrieve data objects with the default paging setting from a table. Returned collection will contain objects of type X (the name of the class must match the name of the table):
Backendless.Persistence.of( X ).find()
  .then( function( result ) {
   })
  .catch( function( error ) {
   });
Find first data object from a table. The name of the class X must match the name of the table. The first data object is the first one saved in the data store:
Backendless.Persistence.of( X ).findFirst()
  .then( function( result ) {
   })
  .catch( function( error ) {
   });
Find last data object from a table. The name of the class X must match the name of the table. The last data object is the last one saved in the data store:
Backendless.Persistence.of( X ).findLast()
  .then( function( result ) {
   })
  .catch( function( error ) {
   });
Find a data object by its ID. The name of the class X must match the name of the table:
Backendless.Persistence.of( X ).findById( objectId )
  .then( function( result ) {
   })
  .catch( function( error ) {
   });

Blocking APIs:

Retrieve data objects with the default paging setting from a table:
Backendless.Data.of( "TABLE-NAME" ).findSync();
Find first data object from a table. The first data object is the first one saved in the data store:
Backendless.Data.of( "TABLE-NAME" ).findFirstSync();
Find last data object from a table. The last data object is the last one saved in the data store:
Backendless.Data.of( "TABLE-NAME" ).findLastSync(); 
Find a data object by its ID:
Backendless.Data.of( "TABLE-NAME" ).findByIdSync( objectId ); 
// Define a function/class which will represent a data table on the server: function X() { // properties of the class defined here } Retrieve data objects with the default paging setting from a table. Returned collection will contain objects of type X (the name of the class must match the name of the table):
Backendless.Data.of( X ).findSync();
Find first data object from a table. The name of the class X must match the name of the table. The first data object is the first one saved in the data store:
Backendless.Data.of( X ).findFirstSync()
Find last data object from a table. The name of the class X must match the name of the table. The last data object is the last one saved in the data store:
Backendless.Data.of( X ).findLastSync()
Find a data object by its ID. The name of the class X must match the name of the table:
Backendless.Data.of( X ).findByIdSync( objectId )

where:

X - a reference to a JavaScript constructor function defining a JavaScript class. Object(s) returned by functions will be of the specified type.
TABLE-NAME - name of the table where to retrieve object(s) from.

Return Value:

The find method returns an array of objects. All other methods return a single object.

Example:

The following code demonstrates various search queries:

Load contacts using default paging:

Non-blocking call:
Backendless.Data.of( "Contact" ).find()
  .then( function( result ) {
     // every loaded object from the "Contact" table is now an individual untyped
     // JS object in the "result" array
   })
  .catch( function( error ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
   });
Blocking call:
// the "result" variable will be a collection of objects
// each object is an untyped JS object representing a Contact
var result = Backendless.Data.of( "Contact" ).findSync();

Find first contact:

Non-blocking call:
Backendless.Data.of( "Contact" ).findFirst()
 .then( function( firstObject ) {
    // first contact instance has been found
  })
 .catch( function( error ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
Blocking call:
var firstContact = Backendless.Data.of( "Contact" ).findFirstSync();

Find last contact:

Non-blocking call:
Backendless.Data.of( "Contact" ).findLast()
 .then( function( lastObject ) {
    // last contact instance has been found
  })
 .catch( function( error ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
Blocking call:
var lastContact = Backendless.Data.of( "Contact" ).findLastSync();

Find contact by objectId:

Non-blocking call:
Backendless.Data.of( "Contact" ).findById( {objectId:"XXXX-XXXX-XXXX-XXXX" } )
 .then( function( contactObject ) {
    // contact instance has been found by its objectId
  })
 .catch( function( error ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
Blocking call:
// retrieve the object using it's objectId
var contact = Backendless.Data.of( "Contact" ).findByIdSync( {objectId:"XXXX-XXXX-XXXX-XXXX" } );
Define the constructor function first:
function Contact()
{
  this.objectId = "";
  this.name = "";
  this.age = 0;
  this.phone = "";
  this.title = "";
}
The following code demonstrates various search queries:

Load contacts using default paging:

Non-blocking call:
Backendless.Data.of( Contact ).find()
  .then( function( result ) {
     // every loaded object from the "Contact" table is now an individual 
     // instance of Contact in the "result" array
   })
  .catch( function( error ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
   });
Blocking call:
// the "result" variable will be an array of objects
// each object is an instance of Contact
var result = Backendless.Data.of( Contact ).findSync();

Find first contact:

Non-blocking call:
Backendless.Data.of( Contact ).findFirst()
 .then( function( firstContact ) {
    // first contact instance has been found
  })
 .catch( function( error ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
Blocking call:
var firstContact = Backendless.Data.of( Contact ).findFirstSync();

Find last contact:

Non-blocking call:
Backendless.Data.of( Contact ).findLast()
 .then( function( lastObject ) {
    // last Contact instance has been found
  })
 .catch( function( error ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
Blocking call:
var lastContact = Backendless.Data.of( Contact ).findLastSync();

Find contact by objectId:

Non-blocking call:
Backendless.Data.of( Contact ).findById( {objectId:"XXXX-XXXX-XXXX-XXXX" } )
 .then( function( contactObject ) {
    // Contact instance has been found by its objectId
  })
 .catch( function( error ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
Blocking call:
// retrieve the object using it's objectId
var contact = Backendless.Data.of( Contact ).findByIdSync( {objectId:"XXXX-XXXX-XXXX-XXXX" } );

 

Advanced Object Retrieval

Advanced search use-cases supported by Backendless include:

Search with a query (a "where clause" search) -retrieves data objects which satisfy a condition.
Paged data retrieval - retrieves a "page" of data of the given size from the specified offset.
Sorted data object retrieval - retrieves a collection of data objects sorted by specified properties.
Retrieval of related objects - fetching data objects through special "relation" properties. See retrieval of related data objects.

 

Backendless supports data retrieval options listed above with a special class - Backendless.DataQueryBuilder. An instance of DataQueryBuilder can be passed into the find operation to retrieve data from the server according to the options set in the query builder object. The class provides the following methods:

create() - creates a new instance of Backendless.DataQueryBuilder.
setProperties( arrayOfStrings ) - sets the names of the properties which values must be retrieved from the server. The argument must be either an array of strings where each element of the array identifies a property/column to be retrieved. Alternatively, the argument can be a string in the following format: "propertyName1, propertyName2, , , propertyNameN".
setWhereClause( stringValue ) - sets a search query. A query must be in the SQL-92 syntax (the "where" clause part).
setSortBy( arrayOfStrings ) - sets an array of column names to sort the data objects in the response by.
setRelated( arrayOfStrings ) - sets an array of related columns names. Objects from the related columns are included into the response.
setRelationsDepth( numericValue ) - sets the number of "levels" in the hierarchy of related objects to include into the response.
setPageSize( numericValue ) - sets the page size - which is the number of objects to return in the response. Maximum value is 100.
setOffset( numericValue ) - sets the offset - an index in the server-side storage from where the data objects should be retrieved.

To run a query-based search, use the following API methods:

Non-blocking Methods:

var queryBuilder = Backendless.DataQueryBuilder.create();

// set query builder properties
// queryBuilder.setXXXX

Backendless.Data.of( "TABLE-NAME" ).find( queryBuilder )
 .then( function( objectArray ) {
  })
 .catch( function( error ) {
 });
// define constructor function. Instances of the DataTypeX class
// are stored in the "DataTypeX" table
function DataTypeX() {
  // define class properties here
}

var queryBuilder = Backendless.DataQueryBuilder.create();

// set query builder properties
// queryBuilder.setXXXX

Backendless.Data.of( DataTypeX ).find( queryBuilder )
 .then( function( dataTypeXobjectArray ) {
  })
 .catch( function( error ) {
 });

Blocking Methods:

var queryBuilder = Backendless.DataQueryBuilder.create();

// set query builder properties
// queryBuilder.setXXXX

var objectCollection = Backendless.Data.of( "TABLE-NAME" ).findSync( queryBuilder );
// define constructor function. Instances of the DataTypeX class
// are stored in the "DataTypeX" table
function DataTypeX() {
  // define class properties here
}

var queryBuilder = Backendless.DataQueryBuilder.create();

// set query builder properties
// queryBuilder.setXXXX

// result is an array of DataTypeX objects
var objectCollection = Backendless.Data.of( DataTypeX ).findSync( queryBuilder );

where:

DataTypeX - a reference to a JavaScript constructor function defining a JavaScript class. Object(s) returned by functions will be of the specified type.
TABLE-NAME - name of the table where to retrieve objects from.
query - an instance of Backendless.DataQueryBuilder described above. The object contains the search query and other search options.

Return Value:

A collection of objects found by the query.

Examples

For examples on using the advanced object retrieval API, see the following sub-sections:

Search with the Where clause

A where clause is a condition which references columns of a data table where the query is sent. For any given query Backendless returns the data records which match the condition in the where clause. A where clause is a query string which must conform to a subset of the SQL-92 standard. The subset of the syntax supported by Backendless is the part that goes into the WHERE clause of an SQL query. This is the reason why we refer to it as the where clause. For example, consider the following data objects, specifically notice the columns name and age:

sample-table.zoom70

 

Suppose you need to retrieve an object where the value of the name property is "Joe". This can be accomplished with the following where clause:

name = 'Joe'

 

Or if you need all objects where the age column contains a value less than 30:

age < 30

 

Just like in SQL, columns can be combined in a single where clause. The following condition would retrieve all objects containing letter J in the name and the age column's value is greater than 20:

name LIKE 'J%' and age > 20

 

Condition

Sample where clause

Column type

Value in a column equals a string literal.

name = 'foo'

STRING, TEXT or EXTENDED STRING

Value in a column does not equal a string literal.

name != 'foo'

STRING, TEXT or EXTENDED STRING

Value in a column is not assigned

name is null

STRING, TEXT or EXTENDED STRING

Value in a column is assigned

name is not null

STRING, TEXT or EXTENDED STRING

Value in a column contains a substring literal

name LIKE '%foo%'

STRING, TEXT or EXTENDED STRING

Value in a column ends with a substring literal

name LIKE '%foo'

STRING, TEXT or EXTENDED STRING

Value in a column starts with a substring literal

name LIKE 'foo%'

STRING, TEXT or EXTENDED STRING

Value in a column is one of enumerated string literals.

name IN ('value1', 'value2', 'value3)
 
or
 
name = 'value1' OR name = 'value2' or name = 'value3'

STRING, TEXT or EXTENDED STRING

Value in a column equals a numeric literal

age = 21

INT or DOUBLE

Value in a column is greater than a numeric literal

age > 21

INT or DOUBLE

Value in a column is greater than or equal a numeric literal

age >= 21

INT or DOUBLE

Value in a column is less than a numeric literal

age < 21

INT or DOUBLE

Value in a column is less than or equal a numeric literal

age <= 21

INT or DOUBLE

 

You do not need to declare/write any custom classes when using the "Untyped Objects" approach. Data records are represented as plain JS objects. Column names become object properties.
Consider the following constructor function:
function Contact()
{
  this.objectId = undefined;
  this.name = "";
  this.age = 0;
  this.phone = "";
  this.title = "";
}

Find all contacts where the value of the "age" property equals 47:

var whereClause = "age = 47";
var queryBuilder = Backendless.DataQueryBuilder.create().setWhereClause( whereClause );

// ***********************************************************
// Non-Blocking API:
// ***********************************************************
Backendless.Data.of( "Contact" ).find( queryBuilder )
 .then( function( foundContacts ) {
    // every loaded object from the "Contact" table is now an individual plain JS object
  })
 .catch( function( fault ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
  
// ***********************************************************
// Blocking API:
// ***********************************************************
var result = Backendless.Data.of( "Contact" ).findSync( queryBuilder );
var whereClause = "age = 47";
var queryBuilder = Backendless.DataQueryBuilder.create().setWhereClause( whereClause );

// ***********************************************************
// Non-Blocking API:
// ***********************************************************
Backendless.Data.of( Contact ).find( queryBuilder )
 .then( function( foundContacts ) {
    // every loaded object from the "Contact" table is now an instance of the Contact class
  })
 .catch( function( fault ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
  
// ***********************************************************
// Blocking API:
// ***********************************************************
var result = Backendless.Data.of( Contact ).findSync( queryBuilder );

Find all contacts where the value of the "age" property is greater than 21:

var whereClause = "age > 21";
var queryBuilder = Backendless.DataQueryBuilder.create().setWhereClause( whereClause );

// ***********************************************************
// Non-Blocking API:
// ***********************************************************
Backendless.Data.of( "Contact" ).find( queryBuilder )
 .then( function( foundContacts ) {
    // every loaded object from the "Contact" table is now an instance of the Contact class
  })
 .catch( function( fault ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
  
// ***********************************************************
// Blocking API:
// ***********************************************************
var result = Backendless.Data.of( "Contact" ).findSync( queryBuilder );
var whereClause = "age > 21";
var queryBuilder = Backendless.DataQueryBuilder.create().setWhereClause( whereClause );

// ***********************************************************
// Non-Blocking API:
// ***********************************************************
Backendless.Data.of( "Contact" ).find( queryBuilder )
 .then( function( foundContacts ) {
    // every loaded object from the "Contact" table is now an instance of the Contact class
  })
 .catch( function( fault ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
  
// ***********************************************************
// Blocking API:
// ***********************************************************
var result = Backendless.Data.of( "Contact" ).findSync( queryBuilder );

Find all contacts by name:

var whereClause = "name = 'Jack Daniels'";
var queryBuilder = Backendless.DataQueryBuilder.create().setWhereClause( whereClause );

// ***********************************************************
// Non-Blocking API:
// ***********************************************************
Backendless.Data.of( "Contact" ).find( queryBuilder )
 .then( function( foundContacts ) {
    // every loaded object from the "Contact" table is now an instance of the Contact class
  })
 .catch( function( fault ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
  
// ***********************************************************
// Blocking API:
// ***********************************************************
var result = Backendless.Data.of( "Contact" ).findSync( queryBuilder );
var whereClause = "name = 'Jack Daniels'";
var queryBuilder = Backendless.DataQueryBuilder.create().setWhereClause( whereClause );

// ***********************************************************
// Non-Blocking API:
// ***********************************************************
Backendless.Data.of( Contact ).find( queryBuilder )
 .then( function( foundContacts ) {
    // every loaded object from the "Contact" table is now an instance of the Contact class
  })
 .catch( function( fault ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
  
// ***********************************************************
// Blocking API:
// ***********************************************************
var result = Backendless.Data.of( Contact ).findSync( queryBuilder );

Find all contacts by partial name match:

var whereClause = "name LIKE 'Jack%'";
var queryBuilder = Backendless.DataQueryBuilder.create().setWhereClause( whereClause );

// ***********************************************************
// Non-Blocking API:
// ***********************************************************
Backendless.Data.of( "Contact" ).find( queryBuilder )
 .then( function( foundContacts ) {
    // every loaded object from the "Contact" table is now an instance of the Contact class
  })
 .catch( function( fault ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
  
// ***********************************************************
// Blocking API:
// ***********************************************************
var result = Backendless.Data.of( "Contact" ).findSync( queryBuilder );
var whereClause = "name LIKE 'Jack%'";
var queryBuilder = Backendless.DataQueryBuilder.create().setWhereClause( whereClause );

// ***********************************************************
// Non-Blocking API:
// ***********************************************************
Backendless.Data.of( Contact ).find( queryBuilder )
 .then( function( foundContacts ) {
    // every loaded object from the "Contact" table is now an instance of the Contact class
  })
 .catch( function( fault ) {
    // an error has occurred, the error code can be retrieved with fault.statusCode
  });
  
// ***********************************************************
// Blocking API:
// ***********************************************************
var result = Backendless.Data.of( Contact ).findSync( queryBuilder );

 

Search with SubQuery

Search with subquery allows your apps to run two queries in the context of a single data retrieval request. The first query identifies a set of objects which is fed into the second query. Consider the following data schema:

users-blogpost-comment

Suppose your app needs to get all blog posts where the person who posted a comment has a specific email address (for example, @backendless.com ). Using subqueries, it can be done with the following whereClause sent to the BlogPost table:
 

objectId in (Comment[author.email LIKE '%@backendless.com'].blogPost.objectId)

 

Let's review how this whereClause is processed by Backendless:

 

1. Backendless detects that the query at the top level uses the in()  operator.
2. It checks if the contents of the in() operator have the format of a subquery and if so, evaluates it accordingly.
3. The subquery contains an "internal whereClause", which is:
author.email LIKE '%@backendless.com'
4. The internal whereClause is applied to the objects in the Comment table.
5. For all found Comment objects, Backendless gets a list of values for the blogPost.objectId column.
6. The resulting set is fed into the in() operator which fetches the final set of objects from the BlogPost table.

 

General SubQuery Syntax

The general syntax for subqueries is:

 

searchColumnNameOrRelatedColumnName IN
  (TableName[internalWhereClause].columnOrRelatedColumnName.optionalcolumnName)

 

How it works:

1. If internalWhereClause  is present, it is executed in the table identified by TableName.
2. In the resulting set of records, values must be selected for the column identified by
columnOrRelatedColumnName.optionalColumnName.
3. If internalWhereClause is not present, the contents of the IN operator have the following syntax. It represents the entire set of values identified by the specified column: TableName.columnOrRelatedColumnName.optionalColumnName
4. The resulting set of values is used in the IN operator, thus the final query ends up being:
searchColumnOrRelatedColumnName IN ( value1, value2,,,valueN )

 

Additional Examples

 

Consider the following data schema:

customer-order-items

1. Get all OrderItem  objects for a customer from New York:
objectId in (Order[customer.address = 'New York'].items.objectId)
 
2. Get all Customer objects who spent more than 1.99 on an item:
objectId in (Order[items.itemPrice > 1.99].customer.objectId)

Aggregate Functions

In addition to retrieving the data from the Backendless database, your app may also need to perform some additional calculations on the data before it is retrieved. For example, you may need to know the average age of users from a geographic area or the sum of all orders containing a specific product. To perform such calculations, you can use aggregate functions in a Backendless query.

 

Backendless supports several aggregate functions, including average, count, sum, min and max.

 

To understand how aggregate functions work in Backendless, consider the following example. It consists of three tables: Movie, Actor and Director.

movies-table-schema

The Movie table stores data about top grossing movies of all time. It also has related columns with the Actor (one to many) and the Director tables (one to one).

top-movies

AVERAGE

The Avg aggregate function allows you to calculate the average value for a set of objects. To use the function, include Avg(columnNameInDatabase) into the list of properties requested from the server. For example, the following query retrieves an average box office earnings value for all movies in the database:

var dataQueryBuilder = Backendless.DataQueryBuilder.create().setProperties( "Avg(totalBoxOffice)");
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The query returns the following result, notice the avg property in the returned object. The value of the property is the mathematical average of all values in the totalBoxOffice column:To change the name of the returned property from avg to a custom name, use the following syntax:

Avg( columnNameInDatabase ) as customPropertyName

For example, the following query returns the average value in the averageTotalBoxOffice property:

var dataQueryBuilder = Backendless.DataQueryBuilder.create()
dataQueryBuilder.setProperties( "Avg(totalBoxOffice) as averageTotalBoxOffice");
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The response for the query now contains the averageTotalBoxOffice property:Results in the response can be grouped by values from another column. To request grouping of the results, add the groupBy parameter to the request with the value containing the name of the column. For example, the following request returns the average box office earnings value for the movies in the database grouped by the year the movies were released:
var dataQueryBuilder = Backendless.DataQueryBuilder.create()
dataQueryBuilder.setProperties( ["Avg(totalBoxOffice)","yearReleased"] );
dataQueryBuilder.setGroupBy( "yearReleased" );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The response for this request contains average earning values for the movies grouped by the release year:

[
   {
       "avg": 2207615668,
       "yearReleased": 1997,
       "___class": "Movie",
   },
   {
       "avg": 2783918982,
       "yearReleased": 2009,
       "___class": "Movie",
   },
   {
       "avg": 1341511219,
       "yearReleased": 2011,
       "___class": "Movie",
   },
   {
       "avg": 1519479547,
       "yearReleased": 2012,
       "___class": "Movie",
   },
   {
       "avg": 1663817556,
       "yearReleased": 2015,
       "___class": "Movie",
   }
]

The results can be sorted using the sortBy parameter. For example, results for the following request will be sorted by the values in the yearReleased column in the descending order:

var dataQueryBuilder = Backendless.DataQueryBuilder.create()
dataQueryBuilder.setProperties( ["Avg(totalBoxOffice)","yearReleased"] );
dataQueryBuilder.setGroupBy( "yearReleased" );
dataQueryBuilder.setSortBy( ["yearReleased DESC"] );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
To sort results by the aggregated value, assign a custom name to the column and group by that name:
var dataQueryBuilder = Backendless.DataQueryBuilder.create()
dataQueryBuilder.setProperties( ["Avg(totalBoxOffice) as averageTotal","yearReleased"] );
dataQueryBuilder.setGroupBy( "yearReleased" );
dataQueryBuilder.setSortBy( ["averageTotal"] );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )

COUNT

The Count aggregate function allows you to calculate the number of objects in a table, optionally satisfying some criteria or grouped by values in a column. To use the function, include Count(columnNameInDatabase) into the list of properties requested from the server. For example, the following query retrieves the count of all movies in the database:

var dataQueryBuilder = Backendless.DataQueryBuilder.create().setProperties( "Count(objectId)");
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The query returns the following result, notice the count property in the returned object. The value of the property is the total count of all objects in the table:To change the name of the returned property from count to a custom name, use the following syntax:

Count( columnNameInDatabase ) as customPropertyName

For example, the following query returns the average value in the totalObjects property:

var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Count(objectId) as totalObjects");
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The response for the query now contains the averageTotalBoxOffice property:Results in the response can be grouped by values from another column. To request grouping of the results, add the groupBy parameter to the request with the value containing the name of the column. For example, the following request returns the number of movies in the database grouped by the year the movies were released:
var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Count(objectId), yearReleased");
dataQueryBuilder.setGroupBy( "yearReleased" );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The response for this request contains average earning values for the movies grouped by the release year:

[
   {
       "count": 1,
       "yearReleased": 1997,
       "___class": "Movie",
   },
   {
       "count": 1,
       "yearReleased": 2009,
       "___class": "Movie",
   },
   {
       "count": 1,
       "yearReleased": 2011,
       "___class": "Movie",
   },
   {
       "count": 1,
       "yearReleased": 2012,
       "___class": "Movie",
   },
   {
       "count": 4,
       "yearReleased": 2015,
       "___class": "Movie",
   }
]

The results can be sorted using the sortBy parameter. For example, results for the following request will be sorted by the values in the yearReleased column in the descending order:

var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Count(objectId), yearReleased");
dataQueryBuilder.setGroupBy( "yearReleased" );
dataQueryBuilder.setSortBy( ["yearReleased DESC"] );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
To sort results by the aggregated value, assign a custom name to the column and group by that name:
var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Count(objectId) as movieCount, yearReleased");
dataQueryBuilder.setGroupBy( "yearReleased" );
dataQueryBuilder.setSortBy( ["movieCount"] );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )

SUM

The Sum aggregate function allows you to calculate the mathematical sum for a set of objects. To use the function, include Sum(columnNameInDatabase) into the list of properties requested from the server. For example, the following query retrieves the sum for all box office earnings for all movies in the database:

var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Sum(totalBoxOffice)");
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The query returns the following result, notice the sum property in the returned object. The value of the property is the mathematical  sum of all values in the totalBoxOffice column:To change the name of the returned property from sum to a custom name, use the following syntax:

Sum( columnNameInDatabase ) as customPropertyName

For example, the following query returns the average value in the grandTotal property:

var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Sum(totalBoxOffice) as grandTotal");
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The response for the query now contains the grandTotal property:Results in the response can be grouped by values from another column. To request grouping of the results, add the groupBy parameter to the request with the value containing the name of the column. For example, the following request returns the sum of  earnings value for the movies in the database grouped by the year the movies were released:
var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Sum(totalBoxOffice), yearReleased");
dataQueryBuilder.setGroupBy( "yearReleased" );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The response for this request contains average earning values for the movies grouped by the release year:

[
   {
       "sum": 2207615668,
       "yearReleased": 1997,
       "___class": "Movie",
   },
   {
       "sum": 2783918982,
       "yearReleased": 2009,
       "___class": "Movie",
   },
   {
       "sum": 1341511219,
       "yearReleased": 2011,
       "___class": "Movie",
   },
   {
       "sum": 1519479547,
       "yearReleased": 2012,
       "___class": "Movie",
   },
   {
       "sum": 6655270224,
       "yearReleased": 2015,
       "___class": "Movie",
   }
]

The results can be sorted using the sortBy parameter. For example, results for the following request will be sorted by the values in the yearReleased column in the descending order:

var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Sum(totalBoxOffice), yearReleased");
dataQueryBuilder.setGroupBy( "yearReleased" );
dataQueryBuilder.setSortBy( ["yearReleased DESC"] );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
To sort results by the aggregated value, assign a custom name to the column and group by that name:
var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Sum(totalBoxOffice) as grandTotal, yearReleased");
dataQueryBuilder.setGroupBy( "yearReleased" );
dataQueryBuilder.setSortBy( ["grandTotal"] );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )

MIN

The Min aggregate function allows you to retrieve the smallest value for a set of objects. To use the function, include Min(columnNameInDatabase) into the list of properties requested from the server. For example, the following query retrieves the smallest box office earnings from the Movies data table:

var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Min(totalBoxOffice)");
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The query returns the following result, notice the min property in the returned object. The value of the property is the smallest amount in the totalBoxOffice column for all movies in the database:The function can be applied not only to the columns containing numerical values. For example, the following query retrieves the "smallest" alphanumeric value for the a movie title:
var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Min(title)");
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The smallest value for the alphanumeric columns is determined by the alphabetical order of the characters. As a result, the movie starting with "A" is the one returned by the server:To change the name of the returned property from min to a custom name, use the following syntax:

Min( columnNameInDatabase ) as customPropertyName

For example, the following query returns the minimum amount in the minimumTotal property:

var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Min(totalBoxOffice) as minimumTotal");
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The response for the query now contains the minimumTotal property:Results in the response can be grouped by values from another column. To request grouping of the results, add the groupBy parameter to the request with the value containing the name of the column. For example, the following request returns the minimum earnings value for the movies in the database grouped by the year the movies were released:
var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Min(totalBoxOffice), yearReleased");
dataQueryBuilder.setGroupBy( "yearReleased" );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The response for this request contains average earning values for the movies grouped by the release year:

[
   {
       "min": 2207615668,
       "yearReleased": 1997,
       "___class": "Movie",
   },
   {
       "min": 2783918982,
       "yearReleased": 2009,
       "___class": "Movie",
   },
   {
       "min": 1341511219,
       "yearReleased": 2011,
       "___class": "Movie",
   },
   {
       "min": 1519479547,
       "yearReleased": 2012,
       "___class": "Movie",
   },
   {
       "min": 1408218722,
       "yearReleased": 2015,
       "___class": "Movie",
   }
]

The results can be sorted using the sortBy parameter. For example, results for the following request will be sorted by the values in the yearReleased column in the descending order:

var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Min(totalBoxOffice), yearReleased");
dataQueryBuilder.setGroupBy( "yearReleased" );
dataQueryBuilder.setSortBy( ["yearReleased DESC"] );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
To sort results by the aggregated value, assign a custom name to the column and group by that name:
var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Min(totalBoxOffice) as minTotal, yearReleased");
dataQueryBuilder.setGroupBy( "yearReleased" );
dataQueryBuilder.setSortBy( ["minTotal"] );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )

MAX

The Max aggregate function allows you to retrieve the largest value for a set of objects. To use the function, include Max(columnNameInDatabase) into the list of properties requested from the server. For example, the following query retrieves the largest box office earnings from the Movies data table:

var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Max(totalBoxOffice)");
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The query returns the following result, notice the max property in the returned object. The value of the property is the largest amount in the totalBoxOffice column for all movies in the database:The function can be applied not only to the columns containing numerical values. For example, the following query retrieves the "largest" alphanumeric value for the a movie title:
var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Max(title)");
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The largest value for the alphanumeric columns is determined by the alphabetical order of the characters. As a result, a movie positioned to be the last one when ordered alphabetically  is the one returned by the server:To change the name of the returned property from min to a custom name, use the following syntax:

Max( columnNameInDatabase ) as customPropertyName

For example, the following query returns the minimum amount in the maximumTotal property:

var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Max(totalBoxOffice) as maxTotal");
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The response for the query now contains the minimumTotal property:Results in the response can be grouped by values from another column. To request grouping of the results, add the groupBy parameter to the request with the value containing the name of the column. For example, the following request returns the maximum earnings value for the movies in the database grouped by the year the movies were released:
var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Max(totalBoxOffice), yearReleased");
dataQueryBuilder.setGroupBy( "yearReleased" );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
The response for this request contains average earning values for the movies grouped by the release year:

[
   {
       "max": 2207615668,
       "yearReleased": 1997,
       "___class": "Movie",
   },
   {
       "max": 2783918982,
       "yearReleased": 2009,
       "___class": "Movie",
   },
   {
       "max": 1341511219,
       "yearReleased": 2011,
       "___class": "Movie",
   },
   {
       "max": 1519479547,
       "yearReleased": 2012,
       "___class": "Movie",
   },
   {
       "max": 2058662225,
       "yearReleased": 2015,
       "___class": "Movie",
   }
]

The results can be sorted using the sortBy parameter. For example, results for the following request will be sorted by the values in the yearReleased column in the descending order:

var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Max(totalBoxOffice), yearReleased");
dataQueryBuilder.setGroupBy( "yearReleased" );
dataQueryBuilder.setSortBy( ["yearReleased DESC"] );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )
To sort results by the aggregated value, assign a custom name to the column and group by that name:
var dataQueryBuilder = Backendless.DataQueryBuilder.create();
dataQueryBuilder.setProperties( "Max(totalBoxOffice) as maxTotal, yearReleased");
dataQueryBuilder.setGroupBy( "yearReleased" );
dataQueryBuilder.setSortBy( ["minTotal"] );
Backendless.Data.of( "Movie" ).find( dataQueryBuilder )
 .then( function( result ) {
   console.log( JSON.stringify( result, null, 2 ) );
 } )

Using Dates in Search

There is a special consideration for the whereClause-based queries which reference a column of the DATETIME data type. Typically a DATETIME column is referenced in a comparison against a scalar value describing a specific date or a timestamp. The scalar value can be a number of milliseconds since the epoch (UNIX timestamp as milliseconds) or a string. Backendless supports a variety of date formats for the latter. For example, the queries below will find all the objects which were updated after March 23rd, 2015:

updated > '23-Mar-2015'

updated > '03/23/2015'

updated > 1427068800000

Comparison Operators

Backendless supports the following date comparison operators:

Column's value is after the specified date/time: use either > or the after keyword:

birthDate > '22-Dec-1980'

birthDate after 1427068800000

Column's value is before the specified date/time: use either < or the before keyword:

birthDate < '22-Dec-1980'

birthDate before 1427068800000

Column's value is either at or after the specified date/time: use either => or the at or after keyword:

birthDate >= '28-10-1988'

birthDate at or after '10/28/1988 00:00:00 GMT-0200'

Column's value is either at or before the specified date/time: use either <= or the at or before keyword:

birthDate >= '28-10-1988'

birthDate at or after '10/28/1988 00:00:00 GMT-0200'

Note: the whereClause-based queries can be tested in the Backendless Console with the SQL Search turned on.

Supported Date Formats

Date/time string values may be in any of the following formats. The pattern letters have the same definition as in Java's SimpleDateFormat:

EEE MMM dd HH:mm:ss zzz yyyy
yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
MM/dd/yyyy HH:mm:ss 'GMT'z
MM.dd.yyyy HH:mm:ss 'GMT'z
MM-dd-yyyy HH:mm:ss 'GMT'z
MM/dd/yyyy HH:mm:ss z
MM.dd.yyyy HH:mm:ss z
MM.dd.yyyy HH:mm:ss
MM-dd-yyyy HH:mm:ss
MM/dd/yyyy HH:mm:ss
MM.dd.yyyy
MM-dd-yyyy
MM/dd/yyyy HH:mm:ss 'GMT'Z
MM/dd/yyyy HH:mm
MM/dd/yyyy
dd/MMM/yyyy
dd-MMM-yyyy
EEEEE, d MMMMM yyyy
yyyy/MM/d/HH:mm:ss
yyyy-MM-dd'T'HH:mm:ss
EEEEE, MMMMM d, yyyy
MMMMM d, yyyy
yyyy M d
yyyyMMMd
yyyy-MMM-d
yyyy-M-d, E
'Date' yyyy-MM-dd
yyyy-MM-dd'T'HH:mm:ssZ
yyyy-MM-dd'T'HH:mmZ
yyyy-MM-dd
yyyy-'W'w
yyyy-DDD
d MMMMM yyyy, HH'h' mm'm' ss's'

Example

var whereClause = "updated after " + timestamp;
var queryBuilder = Backendless.DataQueryBuilder.create().setWhereClause( whereClause );

// Non-Blocking API
Backendless.Data.of( "Contact" ).find( queryBuilder )
 .then( function( result ) {
   // result is an array of plain JS objects
  })
 .catch( function( error ) {
  });

// Blocking API
var result = Backendless.Data.of( "Contact" ).findSync( queryBuilder );
// define constructor function first:
function Contact() {
  // define Contact properties here
}

var whereClause = "updated after " + timestamp;
var queryBuilder = Backendless.DataQueryBuilder.create().setWhereClause( whereClause );

// Non-Blocking API
Backendless.Data.of( Contact ).find( queryBuilder )
 .then( function( result ) {
   // result is an array of Contact objects
  })
 .catch( function( error ) {
  });

// Blocking API
// result will be an array of Contact objects
var result = Backendless.Data.of( Contact ).findSync( queryBuilder );

 

Data retrieval with Paging

Backendless operations returning a collection of objects automatically break up the complete result set into pages. Backendless paging uses two parameters to enable paged data retrieval:

page size - identifies how many objects a single page contains
offset - identifies the position of the object from which to retrieve the number of records identified by page size

 

The diagrams below illustrate the paging process. The first diagram shows the first request to retrieve a collection of records. The request includes the parameters of 5 objects in the page, starting from offset 0:

findrequest1

 

To retrieve the next page of data, the client must set the offset to the index of the first not retrieved object, which is 5:

findrequest2

 

Backendless server sets the maximum allowed value for the page size to 100 objects. The minimum value is 1.

 

JavaScript applications must use the Backendless.DataQueryBuilder class to set the paging parameters. The example below configures DataQueryBuilder to load 25 objects starting from offset 50. The query is used to retrieve objects from the Person table:

var queryBuilder = Backendless.DataQueryBuilder.create();
queryBuilder.setPageSize( 25 ).setOffset( 50 );
Backendless.Data.of( "Person" ).find( queryBuilder )
 .then( function( result ) {
      // the "result" object is an array of plain JS objects.
      // each object in the array represents an object from the "Person" table
    })
 .catch( function( error ) {
       // use the .statusCode or .message on the error object
       // to see the details of the error
    });
var queryBuilder = Backendless.DataQueryBuilder.create();
queryBuilder.setPageSize( 25 ).setOffset( 50 );
Backendless.Data.of( Person ).find( queryBuilder )
 .then( function( result ) {
      // the "result" object is an array of Person instances.
      // each object in the array represents an object from the "Person" table
    })
 .catch( function( error ) {
       // use the .statusCode or .message on the error object
       // to see the details of the error
    });

Application can use the same queryBuilder object to recalculate offset (and if necessary change page size) and use it to retrieve additional pages from the server using the same data retrieval method:

// calculate offset to get the next page of data
queryBuilder.prepareNextPage();

// retrieve the next page of data using the same API as for the first page
Backendless.Data.of( "Person" ).find( queryBuilder )
 .then( function( result ) {
      // the "result" object is an array of plain JS objects.
      // each object in the array represents an object from the "Person" table
    })
 .catch( function( error ) {
       // use the .statusCode or .message on the error object
       // to see the details of the error
    });
// calculate offset to get the next page of data
queryBuilder.prepareNextPage();

// retrieve the next page of data using the same API as for the first page
Backendless.Data.of( Person ).find( queryBuilder )
 .then( function( result ) {
      // the "result" object is an array of Person instances.
      // each object in the array represents an object from the "Person" table
    })
 .catch( function( error ) {
       // use the .statusCode or .message on the error object
       // to see the details of the error
    });

 

 

 

Sorting

Data retrieval and search API can request the server to return sorted data. Data can be sorted by one or more columns. The sorting direction can be either ascending (default) or descending for each individual column. JavaScript applications must use the Backendless.DataQueryBuilder class to set the sorting option. The example below configures DataQueryBuilder to sort results by the name and age columns. Value in  the age column are sorted in the descending order. The query is used to retrieve objects from the Person table:

var queryBuilder = Backendless.DataQueryBuilder.create();
queryBuilder.setSortBy( ["name", "age DESC" ] );
Backendless.Data.of( "Person" ).find( queryBuilder )
  .then( function( result ) {
      // the "result" object is an array of plain JS objects.
      // each item in the array represents an object from the "Person" table
    })
  .catch( function( error ) {
       // use the .statusCode or .message on the "error" object
       // to see the details of the error
  });
var queryBuilder = Backendless.DataQueryBuilder.create();
queryBuilder.setSortBy( ["name", "age DESC"] );
Backendless.Data.of( Person ).find( queryBuilder )
  .then( function( result ) {
      // the "result" object is an array of Person objects.
      // each item in the array represents an object from the "Person" table
    })
  .catch( function( error ) {
       // use the .statusCode or .message on the "error" object
       // to see the details of the error
  });

The descending sorting order can be requested by adding DESC to the name of the column (separated by space).

Search by distance

With the ability to link data objects with geo points, you can search for data objects by distance. This type of search returns a paged set of data objects which satisfy the whereClause condition and located within the specified distance. Distance-based search uses a special function in whereClause of the search request. The syntax of the function is:

distance( 
   center point latitude, 
   center point longitude, 
   columnname which contains geo point.latitude,
   columnname which contains geo point.longitude )<operator> units-function(value)

where:

<operator> - Possible values are <, >, =, >=, <=
units-function - Defines the units of measure for the distance. Possible values are:
ft( X ) - the distance value X is expressed in feet
km( X ) - the distance value X is expressed in kilometers
mi( X ) - the distance value X is expressed in miles
yd( X ) -  the distance value X is expressed in yards

For example, the following whereClause expression searches for data objects located within 200 miles from the point at 30.26715, -97.74306. Each data object must have the "coordinates" property of type GeoPoint.

distance( 30.26715, -97.74306, coordinates.latitude, coordinates.longitude ) < mi(200)

The following example demonstrates a search-by-distance query. The example uses three data objects stored in the Friend table: Bob, Jane, and Fred who respectively live in Austin, Houston, San Antonio. The search query in the example finds all friends who live within the specified distance. Before running the search query, create the objects in the data storage with the corresponding geo points.

 

The application must be setup with demo data in order to run the example and see the distance operator in action. See the setup code in the expandable block below:

You do not need to declare/write any custom classes/function when using the "Untyped Objects" approach. Data records are represented as plain JS objects. Column names become object properties.
The Friend class definition:
function Friend()
{
  this.name = "";
  this.phoneNumber = "";
  this.coordinates = undefined;
}

 

Run the following query/code to store a data object representing Bob with a link to his home in Austin, TX:

// Create an object representing a geopoint. The ___class property is special, 
// it tells Backendless that the object is a GeoPoint and must be stored in
// the Backendless geo storage.
var bobsHomeLocation = {
  ___class : "GeoPoint",
  latitude: 29.76328,
  longitude: -95.36327,
  categories: [ "Home" ],
  metadata: { description:"Bob's home" }
}

// Notice the "coordinates" property, it links the object with 
// the corresponding geopoint
var bob = { 
  name:"Bob",
  phoneNumber:"512-555-1212",
  coordinates: bobsHomeLocation
};

// Save "bob" and his location in the Backendless geostorage
Backendless.Data.of( "Friend" ).save( bob )
 .then( function( result ) {
  console.log( "object and its geopoint location have been saved" );
 })
 .catch( function( error ) {
   console.log( "error " + error.message );
});
var geopoint = new Backendless.GeoPoint();
geopoint.latitude = 29.76328;
geopoint.longitude = -95.36327;
geopoint.categories = ["Home"];
geopoint.metadata = { description:"Bob's home" };
  
var bob = new Friend();
bob.name = "Bob";
bob.phoneNumber = "512-555-1212";
bob.coordinates = geopoint;

Backendless.Data.of( Friend ).save( bob )
 .then( function( result ) {
   console.log( "object bob with the related geopoint has been saved" );
 })
 .catch( function( error ) {
   console.log( "error " + error.message );
 });

 

Run the following query/code to store a data object representing Jane with a link to her home in Houston, TX:

// Create an object representing a geopoint. The ___class property is special, 
// it tells Backendless that the object is a GeoPoint and must be stored in
// the Backendless geo storage.
var janesHomeLocation = {
  ___class : "GeoPoint",
  latitude:29.76328,
  longitude: -95.36327,
  categories: [ "Home" ],
  metadata: { description:"Jane's home" }
}

// Notice the "coordinates" property, it links the object with 
// the corresponding geopoint
var jane = { 
  name:"Jane",
  phoneNumber:"281-555-1212",
  coordinates: janesHomeLocation
};

// Save "jane" and her location in the Backendless geostorage
Backendless.Data.of( "Friend" ).save( jane )
 .then( function( result ) {
  console.log( "object and its geopoint location have been saved" );
 })
 .catch( function( error ) {
   console.log( "error " + error.message );
});
var geopoint = new Backendless.GeoPoint();
geopoint.latitude = 29.76328;
geopoint.longitude = -95.36327;
geopoint.categories = ["Home"];
geopoint.metadata = { description:"Jane's home" };
  
var jane = new Friend();
jane.name = "Jane";
jane.phoneNumber = "281-555-1212";
jane.coordinates = geopoint;

Backendless.Data.of( Friend ).save( jane )
 .then( function( result ) {
   console.log( "object jane with the related geopoint has been saved" );
 })
 .catch( function( error ) {
   console.log( "error " + error.message );
 });

 

Run the following query/code to store a data object representing Fred with a link to his home in San Antonio, TX:

// Create an object representing a geopoint. The ___class property is special, 
// it tells Backendless that the object is a GeoPoint and must be stored in
// the Backendless geo storage.
var fredsHomeLocation = {
  ___class : "GeoPoint",
  latitude:29.42412,
  longitude: -98.49363,
  categories: [ "Home" ],
  metadata: { description:"Fred's home" }
}

// Notice the "coordinates" property, it links the object with 
// the corresponding geopoint
var fred = { 
  name:"Fred",
  phoneNumber:"210-555-1212",
  coordinates: fredsHomeLocation
};

// Save "fred" and his location in the Backendless geostorage
Backendless.Data.of( "Friend" ).save( fred )
 .then( function( result ) {
  console.log( "object and its geopoint location have been saved" );
 })
 .catch( function( error ) {
   console.log( "error " + error.message );
});
var geopoint = new Backendless.GeoPoint();
geopoint.latitude = 29.42412;
geopoint.longitude = -98.49363;
geopoint.categories = ["Home"];
geopoint.metadata = { description:"Fred's home" };
  
var fred = new Friend();
fred.name = "Jane";
fred.phoneNumber = "210-555-1212";
fred.coordinates = geopoint;

Backendless.Data.of( Friend ).save( fred )
 .then( function( result ) {
   console.log( "object fred with the related geopoint has been saved" );
 })
 .catch( function( error ) {
   console.log( "error " + error.message );
 });

 

 

Once the data is in the persistent object and geo location storage, you can verify it in Backendless Console by opening the Geolocation screen and selecting the Home geocategory:

distance-search

 

Suppose you need to get all objects located within 300 miles radius from Beaumont, TX, which has the GPS coordinates of 30.084, -94.145. The following code/query performs that distance-based search:

var whereClause = "distance( 30.26715, -97.74306, " + 
                  "coordinates.latitude, " +
                  "coordinates.longitude ) < mi(200)";
var queryBuilder = DataQueryBuilder.create();
queryBuilder.setWhereClause( whereClause ).setRelationsDepth( 1 );
var friends = Backendless.Data.of( "Friend" ).find( queryBuilder );

friends.forEach( function( friend ) {
  console.log(  friend.name + 
                " lives at " +
                friend.coordinates.latitude + " , " +
                friend.coordinates.longitude + " " +
                friend.coordinates.metadata.description); 
});
var whereClause = "distance( 30.26715, -97.74306, " + 
                  "coordinates.latitude, " + 
                  "coordinates.longitude ) < mi(200)";
var queryBuilder = DataQueryBuilder.create();
queryBuilder.setWhereClause( whereClause ).setRelationsDepth( 1 );
var friends = Backendless.Data.of( Friend ).find( queryBuilder );

friends.forEach( function( friend ) {
  console.log(  friend.name + 
                " lives at " +
                friend.coordinates.latitude + " , " +
                friend.coordinates.longitude + " " +
                friend.coordinates.metadata.description); 
});

The search returns all data objects within the specified distance. Each data object has the Coordinates property containing the coordinates of a geo point associated with this data object.

Relations Overview

A data object stored in Backendless may reference other objects from the same or other data tables. These references are called relations. When an object references a single object, it is a one-to-one relation. If a reference is for a collection of objects, it is a one-to-many relation. Relations can be established using either API or Backendless Console. When working with console, a relation in a table must be declared through a relation column. Console allows two types of relation columns: data object relationship and geopoint relationship. The former identifies a relation between two data tables, while the later declares a relationship between a data table and geopoint storage. It is not required to declare relation columns in console - Backendless can derive these relations from the API calls and subsequently creates columns in data tables.

 

The rest of this chapter describes relationship management using Backendless console. For the API reference, see the corresponding chapters.

Declaring a Relation Between Tables

Creating Relations Between Objects in Related Tables

Editing (Update/Delete) Relations

Deleting Relation Column

Declaring a Relation Between Tables

The instructions below describe how to declare a relation between two tables. As an example, the instructions below create a relation column address in the Person table. The new column will reference the Address table:

1. Select the Person table where a relation column should be declared.
2. Click the Schema menu.
3. Click the New button. The pop-up window will display as shown below:

adding-new-column.zoom80

4. Enter column name that will identify the relation.
5. Click the Type drop-down list and select the Data Object Relationship option.
6. Select a related table and the cardinality of the relations from the corresponding drop-down menus. The one-to-one relation means that a table's object can be linked with only one object from the related table. The one-to-many relation means that a table's object can be linked with more than one object from the related table.

relationship-column.zoom80

7. Click CREATE to save the changes.

Once a relationship column is declared, it will appear along other columns in the Data Browser view of the Backendless Console.

Creating Relations Between Objects in Related Tables

Once you have declared a relation between table schemas, you can establish a relation between objects in these tables. Follow the instructions below to link the objects:

1. Click the table name where you declared a relation. Console displays the columns representing relations slightly different than the other ones. The header for these columns includes:
- name of the related table;
- relation type (cardinality) visualized as either a single red line for one-to-one relations or three red lines for one-to-many relations;
- the "auto-load" checkbox.

relation-column-data-browser.zoom70

2. Every object in the table displays a plus icon on mouse hover which can be used to create a relation between that object and one from the related table:
plus-for-relation
3. Click the plus icon for the object to create a relation. The Set Related Object pop-up window will display the list of objects from the related table.

set-relation-popup.zoom70

4. Each object in the displayed popup has either a radio button or a checkbox next to the object's data. Radio buttons are used for one-to-one relations, while checkboxes apply for the one-to-many relations. Select the object(s) which will be linked to the parent object.
5. Click the ADD RELATION button to save the changes.
6. Once a relation between the objects is established, the related object(s) appear as hyperlinks which can be clicked to navigate to them:
relation-created

Editing (Update/Delete) Relations

You can edit the relations between the data objects. Editing a relation allows changing the related object or breaking the relation between the objects.

1. Click the name of the table containing the object with relations. Click the plus icon in the cell displaying the related object (the same plus icon you used to create a relation above):

editing-relation-plus-icon.zoom80

2. The Set Related Object pop-up window will open and display the related object. If you want to link a data object with other object(s), click the radio-button or check-box(s) next to the necessary object(s).
remove-relation.zoom70
3. Depending on the made changes, the action button at the bottom of the popup will say either REMOVE RELATION or UPDATE RELATION. Click the button to finalize the changes.

Deleting Relation Column

When a relation column is deleted, all references between the related objects are also removed. This operation does not delete the actual objects - it removes the column and any references between the related objects.

To delete a relation column using the Backendless Console:

1. Click the name of the table where you want to delete a relation column.
2. Click the Schema menu
3. Click the check-box next to the column you need to delete.
4. Click the Delete menu:

delete-relation-column

 

Relations API (Set/Add)

This and other relation API sections refer to objects in a relation as parent and child(ren). The parent object is the one that contains a reference to a related object. A child object is one that is being referenced by the parent.

There are two operations supporting creating a relation between objects - setting a relation and adding a relation:

Setting a relation - sets specified child objects as the related objects for the parent. If the parent had other child objects prior to the operation, the relation between them and the parent is removed. This is a replacement operation.
Adding a relation - this operation adds specified child objects to the collection of existing child objects. This is a concatenation operation. For one-to-one relations, if the parent object has a child for the specified column at the time when the operation is called, an error is returned back to the client

 

Other implementation details:

Both parent and child objects must exist in the Backendless storage.
If a column identifies a 1:1 relation which already exists between the parent and some other object, the "set" operation must replace the child object with the one identified in the operation. The "add" operation must return an error.
Add/set methods must return the number of objects added/set, that is, for example, if a whereClause is used and identifies an object (among others) which is already in the relation, that object must not be "accounted for" in the returned number of object.
If add/set receives a non-existing object, it must be ignored and the returned count of objects set/added must not reflect the ignored object.
Both operations create the relation column if it does not exist at the time of the method call.
 

There are two ways to add/set a relation:

Explicit relation list - an API request identifies specific objects to be added/set as children of the parent object. Available APIs:
Set Relation
Add Relation
Implicit relation list - an API request uses a query (the where clause) to identify objects which should be added/set as children of the parent object. Available APIs:
Set Relation
Add Relation

Set Relation with objects

API request replaces related objects with the ones identified in the API call. Child objects must be explicitly defined by referencing their IDs.

Non-blocking Method:

Backendless.Data.of( "TABLE-NAME" ).setRelation( 
     parentObject,
     relationColumnName,
     childrenArray )
 .then( function( count ) {
  })
 .catch( function( error ) {
  });
Backendless.Data.of( ClassFunctionRef ).setRelation( 
     parentObject,
     relationColumnName,
     childrenArray )
 .then( function( count ) {
  })
 .catch( function( error ) {
  });

Blocking Method:

var count = Backendless.Data.of( "TABLE-NAME" ).setRelationSync( 
          parentObject,
          relationColumnName,
          childrenArray );
var count = Backendless.Data.of( ClassFunctionRef ).setRelationSync( 
                parentObject,
                relationColumnName,
                childrenArray );

where:

TABLE-NAME - name of the table where the parent object is stored.
ClassFunctionRef - reference to a JS function/class identifying the table. Name of the table must match the name of the function.
parentObject - the object which will be assigned related children for relatedColumnName. When this argument is a plain JS  object(for the "Untyped Objects" approach), it must contain the "objectId" property.
relationColumnName - name of the column identifying the relation. Objects from the childrenArray array will be set as related for the  column in parentObject. The column name may optionally include table name separated by the colon character as well as cardinality which determines the type of relationship (one to one or one to many) (see the note below):

If the column does not exist in the parent table at the time when the API is called, the value of the "relationColumnName" argument must include the name of the child table separated by colon and the cardinality notation. The cardinality is expressed as ":1" for one-to-one relations and ":n" for one-to-many relations. For example, the value of "myOrder:Order:1" will create a one-to-one relation column "myOrder" in the parent table. The column will point to the Order child table. Likewise, the value of "myOrder:Order:n" will create a one-to-many relation column "myOrder" pointing to the Order table.

childrenArray - an array of child objects to set into the relation identified by relatedColumnName. For the one-to-one relations the collection must contain one element. The array element can be either a complete object which must have the objectId property or a string value of the objectId property. Both of the following examples are valid values for the childrenArray argument:
[ "XXXX-XXXX-XXXX-XXXX", "ZZZZ-ZZZZ-ZZZZZ-ZZZZ" ]
[ {objectId:"XXXX-XXXX-XXXX-XXXX"}, {objectId:"ZZZZ-ZZZZ-ZZZZZ-ZZZZ" }]

Return Value:

Number of child objects set into the relation.

Example:

The example below sets a relation for a one-to-one column named address declared in the Person table. The column must be declared in the table prior to the execution of the request shown below. This necessity is explained by missing table name qualifier in the relationColumnName argument - notice the relation column is "address". If the argument value were "address:Address:1", then the column would be created automatically.
relation-column
 

var parentObject = { objectId:"41230622-DC4D-204F-FF5A-F893A0324800" };

var childObject = { objectId:"3464C734-F5B8-09F1-FFD3-18647D12E700" };

var children = [ childObject ];

Backendless.Data.of( "Person" ).setRelation( parentObject, "address", children )
  .then( function( count ) {
    console.log( "relation has been set" );
  })
  .catch( function( error ) {
    console.log( "server reported an error - " + error.message );
  });
function Person() {
  // define Person properties here
}

function Address() {
  // define Address properties here
}

var personObject = // personObject retrieval is out of scope in this example
var addressObject = // addressObject retrieval is out of scope in this example

var addressCollection = [ addressObject ];

Backendless.Data.of( Person ).setRelation( personObject, "address", addressCollection )
  .then( function( count ) {
    console.log( "relation has been set" );
  })
  .catch( function( error ) {
    console.log( "server reported an error - " + error.message );
  });

Set Relation with condition

API request replaces related objects with the ones identified in the API call. Child objects are referenced implicitly - through "whereClause" defining the condition for object selection.

Non-blocking Method:

Backendless.Data.of( "TABLE-NAME" ).setRelation( 
    parentObject,
    relationColumnName,
    whereClause )
 .then( function( count ) {
  })
 .catch( function( error ) {
 });
Backendless.Data.of( ClassFunctionRef ).setRelation( 
      parentObject,
      relationColumnName,
      whereClause )
 .then( function( count ) {
  })
 .catch( function( error ) {
 });

Blocking Method:

var count = Backendless.Data.of( "TABLE-NAME" ).setRelationSync( 
     parentObject,
     relationColumnName,
     whereClause );
var count = Backendless.Data.of( ClassFunctionRef ).setRelationSync( 
    parentObject,
    relationColumnName,
    whereClause );

where:

TABLE-NAME - name of the table where the parent object is stored.
ClassFunctionRef - reference to a JS function/class identifying the table. Name of the table must match the name of the function.
parentObject - the object which will be assigned related children for relatedColumnName. When this argument is a plain JS  object(for the "Untyped Objects" approach), it must contain the "objectId" property.
relationColumnName - name of the column identifying the relation. Objects from the childrenArray array will be set as related for the  column in parentObject. The column name may optionally include table name separated by the colon character (see the note below):

If the column does not exist in the parent table at the time when the API is called, the value of the "relationColumnName" argument must include the name of the child table separated by colon and the cardinality notation. The cardinality is expressed as ":1" for one-to-one relations and ":n" for one-to-many relations. For example, the value of "myOrder:Order:1" will create a one-to-one relation column "myOrder" in the parent table. The column will point to the Order child table. Likewise, the value of "myOrder:Order:n" will create a one-to-many relation column "myOrder" pointing to the Order table.

whereClause - a where clause condition identifying objects in the child table which will be set as the related objects for the parent object.

Return Value:

Number of child objects set into the relation.

Example:

The following request creates a relation between a Person object and all objects in the Users table which match the provided query. The query is specified in the whereClause argument:

name='Joe' or name = 'Frank'.

As a result of the operation, all User objects where the name property is either Joe or Frank will be set in the relation. The relation column is created if it does not exist. This is done because the column name argument contains the child table qualifier, defined as ":Users:n" right after the column name.

var parentObject = { objectId:"41230622-DC4D-204F-FF5A-F893A0324800" };

Backendless.Data.of( "Person" ).setRelation( parentObject, 
                                             "users:Users:n", 
                                             "name = \"Joe\" or name = \"Frank\"" )
 .then( function( count ) {
    console.log( "relation has been set" );
  })
 .catch( function( error ) {
    console.log( "server reported an error - " + error.message );
  });
function Person {
  // define Person class properties here
}

var personObject = // personObject retrieval is out of scope in this example

Backendless.Data.of( Person ).setRelation( parentObject, 
                                             "users:Users:n", 
                                             "name = \"Joe\" or name = \"Frank\"" )
 .then( function( count ) {
    console.log( "relation has been set" );
  })
 .catch( function( error ) {
    console.log( "server reported an error - " + error.message );
  });

Add Relation with objects

This API request adds related objects to the existing collection. Child objects to add to the relation must be explicitly defined by referencing their IDs.

Non-blocking Method:

Backendless.Data.of( "TABLE-NAME" ).addRelation( 
    parentObject,
    relationColumnName,
    childrenArray )
 .then( function( count ) {
  })
 .catch( function( error ) {
  });
Backendless.Persistence.of( DataTypeX ).addRelation( 
                                   parentObject,
                                   relationColumnName,
                                   childrenArray)
  .then( function( count ) {
   })
  .catch( function( error ) {
  });

Blocking Method:

var count = Backendless.Data.of( "TABLE-NAME" ).addRelationSync( 
             parentObject,
             relationColumnName,
             childrenArray );
var count = Backendless.Data.of( DataTypeX ).addRelationSync( 
              parentObject,
              relationColumnName,
              childrenArray );

 

where:

TABLE-NAME - name of the table where the parent object is stored.
DataTypeX - reference to a JS function/class identifying the table. Name of the table must match the name of the function.
parentObject - the object which will receive related children for relatedColumnName. When this argument is a plain JS  object(for the "Untyped Objects" approach), it must contain the "objectId" property.
relationColumnName - name of the column identifying the relation. Objects from the childrenArray array will be added as related for the  column in parentObject. The column name may optionally include table name separated by the colon character (see the note below):

If the column does not exist in the parent table at the time when the API is called, the value of the "relationColumnName" argument must include the name of the child table separated by colon and the cardinality notation. The cardinality is expressed as ":1" for one-to-one relations and ":n" for one-to-many relations. For example, the value of "myOrder:Order:1" will create a one-to-one relation column "myOrder" in the parent table. The column will point to the Order child table. Likewise, the value of "myOrder:Order:n" will create a one-to-many relation column "myOrder" pointing to the Order table.

childrenArray - an array of child objects to add to the relation identified by relatedColumnName. For the one-to-one relations the collection must contain one element. The array element can be either a complete object which must have the objectId property or a string value of the objectId property. Both of the following examples are valid values for the childrenArray argument:
[ "XXXX-XXXX-XXXX-XXXX", "ZZZZ-ZZZZ-ZZZZZ-ZZZZ" ]
[ {objectId:"XXXX-XXXX-XXXX-XXXX"}, {objectId:"ZZZZ-ZZZZ-ZZZZZ-ZZZZ" }]

Return Value:

Number of child objects added to the relation.

Example:

The example below adds a related object for a one-to-one column named address declared in the Person table. If the relation already contains an object, server returns an error since the addRelation API for one-to-one relations can add an object only once. The column must be declared in the table prior to the execution of the request shown below. This necessity is explained by missing table name qualifier in the relationColumnName argument - notice the relation column name is "address". If it were specified as "address:Address:1", then the column would be created automatically.
relation-column
 

var parentObject = { objectId:"41230622-DC4D-204F-FF5A-F893A0324800" };

var childObject = { objectId:"3464C734-F5B8-09F1-FFD3-18647D12E700" };

var children = [ childObject ];

Backendless.Data.of( "Person" ).addRelation( parentObject, "address", children )
  .then( function( count ) {
    console.log( "relation has been set" );
  })
  .catch( function( error ) {
    console.log( "server reported an error - " + error.message );
  });
function Person() {
  // define Person properties here
}

function Address() {
  // define Address properties here
}

var personObject = // personObject retrieval is out of scope in this example
var addressObject = // addressObject retrieval is out of scope in this example

var addressCollection = [ addressObject ];

Backendless.Data.of( Person ).setRelation( personObject, "address", addressCollection )
  .then( function( count ) {
    console.log( "relation has been set" );
  })
  .catch( function( error ) {
    console.log( "server reported an error - " + error.message );
  });

Add Relation with condition

This API request adds related objects to the existing collection. Child objects to add to the relation are defined through a whereClause condition.

Non-blocking Method:

Backendless.Data.of( "TABLE-NAME" ).addRelation( 
       parentObject,
       relationColumnName,
       whereClause )
 .then( function( count ) {
  })
 .catch( function( error ) {
  });
Backendless.Data.of( DataTypeX ).addRelation( 
     parentObject,
     relationColumnName,
     whereClause )
  .then( function( count ) {
   })
  .catch( function( error ) {
   });

Blocking Method:

var count = Backendless.Data.of( "TABLE-NAME" ).addRelationSync( 
               parentObject,
               relationColumnName,
               whereClause );
var count = Backendless.Data.of( DataTypeX ).addRelationSync( 
                parentObject,
                relationColumnName,
                whereClause );

where:

TABLE-NAME - name of the table where the parent object is stored.
DataTypeX - reference to a JS function/class identifying the table. Name of the table must match the name of the function.
parentObject - the object which will receive related children for relatedColumnName. When this argument is a plain JS  object(for the "Untyped Objects" approach), it must contain the "objectId" property.
relationColumnName - name of the column identifying the relation. Objects identified by the whereClause argument will be added as related objects for the  column in parentObject. The column name may optionally include table name separated by the colon character (see the note below):

If the column does not exist in the parent table at the time when the API is called, the value of the "relationColumnName" argument must include the name of the child table separated by colon and the cardinality notation. The cardinality is expressed as ":1" for one-to-one relations and ":n" for one-to-many relations. For example, the value of "myOrder:Order:1" will create a one-to-one relation column "myOrder" in the parent table. The column will point to the Order child table. Likewise, the value of "myOrder:Order:n" will create a one-to-many relation column "myOrder" pointing to the Order table.

whereClause - a where clause condition identifying objects in the child table which will be added as related objects to the parent object.

Return Value:

Number of child objects added to the relation.

Example:

The following request adds objects from the Users table to the related property/column the Person object. Child objects added to the relation  must match the provided query. The query is specified in the whereClause argument:

name='Joe' or name = 'Frank'.

As a result of the operation, all User objects where the name property is either Joe or Frank will be added to the relation. The relation column is created if it does not exist. This is done because the column name argument contains the child table qualifier, defined as ":Users:n" right after the column name.

var  parentObject { objectId:"41230622-DC4D-204F-FF5A-F893A0324800" };

Backendless.Data.of( "Person" ).addRelation( parentObject, 
                                             "users:Users:n", 
                                             "name = \"Joe\" or name = \"Frank\"" )
 .then( function( count ) {
    console.log( "related objects have been added" );
  })
 .catch( function( error ) {
    console.log( "server reported an error - " + error.message );
  });
Person personObject = // personObject retrieval is out of scope in this example

Backendless.Data.of( Person.class ).addRelation( personObject, 
                                                 "users:Users:n", 
                                                 "name = \"Joe\" or name = \"Frank\"",
        new AsyncCallback<Integer>()
        {
          @Override
          public void handleResponse( Integer response )
          {
            Log.i( "MYAPP", "related objects have been added");
          }

          @Override
          public void handleFault( BackendlessFault fault )
          {
            Log.e( "MYAPP", "server reported an error - " + fault.getMessage() );
          }
        } );

Relations API (Delete)

This and other relation API sections refer to objects in a relation as parent and child(ren). The parent object is the one that contains a reference to a related object. A child object is one that is being referenced by the parent.

Relation deletion API disconnects a relation between the parent object and some (or all) related children. The API does not delete any objects, it is responsible strictly for breaking the relationship. For object deletion API, see the Deleting Data Objects section of the documentation.

 

There are two ways to delete a relation:

Explicit deletion list - an API request identifies specific child objects to be disconnected from the parent object.

Implicit deletion list - an API request uses a condition (the where clause) to identify the child objects which should be disconnected from the parent object.

Delete Relation with objects

API request must identify the child objects to delete from the relation explicitly, by referencing their IDs.

Non-blocking Method:

Backendless.Data.of( "TABLE-NAME" ).deleteRelation( 
      parentObject,
      relationColumnName,
      childrenArray )
  .then( function( count ) {
   })
  .catch( function( error ) {
   });
Backendless.Data.of( DataTypeX ).deleteRelation( 
       parentObject,
       relationColumnName,
       childrenArray )
 .then( function( count ) {
  })
 .catch( function( error ) {
  });

Blocking Method:

var count = Backendless.Data.of( "TABLE-NAME" ).deleteRelationSync( 
               parentObject,
               relationColumnName,
               childrenArray );
var count = Backendless.Data.of( DataTypeX ).deleteRelationSync( 
                parentObject,
                relationColumnName,
                childrenArray );

where:

TABLE-NAME - name of the table where the parent object is stored.
DataTypeX - reference to a JS function/class identifying the table. Name of the table must match the name of the function.
parentObject - The object for which the relation with the specified children will be deleted. When this argument is a plain  JS object (for the "Untyped Objects" approach), it must contain the "objectId" property.
relationColumnName - name of the column identifying the relation. Relation between the parent object and the objects from the childrenArray array for the  column in parentObject will be deleted.
childrenArray - an array of child objects for which the relation with parentObject will be deleted.

Return Value:

Number of child objects for which the relationship has been deleted.

Example:

The example below deletes a relation between an object from the Person table and its children. The child objects are referenced explicitly in the API call (see the object IDs in the collection as "XXXXX-XXXXX-XXXXX-XXXXX" and "ZZZZ-ZZZZ-ZZZZZ-ZZZZZ").The relation column is address.

var parentObject = { objectId:"41230622-DC4D-204F-FF5A-F893A0324800"};
var childObject1 = { objectId:"XXXX-XXXX-XXXX-XXXXX" };
var childObject2 = { objectId:"ZZZZ-ZZZZ-ZZZZZ-ZZZZZ" };
var children = [ childObject1, childObject2 ];

Backendless.Data.of( "Person" ).deleteRelation( parentObject, "address", children )
 .then( function( count ) {
    console.log( "relation has been deleted" );
  })
 .catch( function( error ) {
    console.log( "server reported an error - " + error.message );
  });
function Person {
  // properties of Person defined here
}
 
function Address {
  // properties of Address defined here
}

var personObject = // personObject retrieval is out of scope in this example
var addressObject1 = // addressObject retrieval is out of scope in this example
var addressObject2 = // addressObject retrieval is out of scope in this example
var addressCollection = [ addressObject1, addressObject2 ];

Backendless.Data.of( Person ).deleteRelation( personObject, "address", addressCollection )
  .then( function( count ) {
     console.log( "relation has been deleted");
   })
  .catch( function( error ) {
     console.log( "server reported an error - " + error.message );
   });

Delete Relation with condition

API request must identify the child objects to delete from the relation implicitly through a whereClause condition.

Non-blocking Method:

Backendless.Data.of( "TABLE-NAME" ).deleteRelation( 
     parentObject,
     relationColumnName,
     whereClause )
  .then( function( count ) {
   })
  .catch( function( error ) {
   });
Backendless.Data.of( DataTypeX ).deleteRelation( 
     parentObject,
     relationColumnName,
     whereClause )
  .then( function( count ) {
   })
  .catch( function( error ) {
   });

Blocking Method:

var count = Backendless.Data.of( "TABLE-NAME" ).deleteRelationSync( 
               parentObject,
               relationColumnName,
               whereClause );
var count = Backendless.Data.of( DataTypeX ).deleteRelationSync( 
                 parentObject,
                 relationColumnName,
                 whereClause );

where:

TABLE-NAME - name of the table where the parent object is stored.
DataTypeX - reference to a JS function/class identifying the table. Name of the table must match the name of the function.
parentObject - The object for which the relation with the specified children will be deleted. When this argument is a plain  JS object (for the "Untyped Objects" approach), it must contain the "objectId" property.
relationColumnName - name of the column identifying the relation. Relation between the parent object and the objects identified  through whereClause for the  column in parentObject will be deleted.
whereClause - a where clause condition identifying the objects in the child table which will be removed from the relation to the parent object.

Return Value:

Number of child objects for which the relationship has been deleted.

Example:

The following request deletes a relation between a Person object and all the related objects in the related table identified by column "user" which match the provided query:

name='Joe' or name = 'Frank'

As a result of the operation, all related objects where the name property is either Joe or Frank will be deleted from the relation.

var parentObject = { objectId:"41230622-DC4D-204F-FF5A-F893A0324800" };

Backendless.Data.of( "Person" ).deleteRelation( parentObject, 
                                                "user", 
                                                "name = \"Joe\" or name = \"Frank\"" )
 .then( function( count ) {
    console.log( "relation has been deleted" );
  })
 .catch( function( error ) {
    console.log( "server reported an error - " + error.message );
  });
function Person {
  // properties of Person defined here
}
   
var parentObject = // retrieval of the parent object is out of scope of the example

Backendless.Data.of( Person ).deleteRelation( parentObject, 
                                                "user", 
                                                "name = \"Joe\" or name = \"Frank\"" )
 .then( function( count ) {
    console.log( "relation has been deleted" );
  })
 .catch( function( error ) {
    console.log( "server reported an error - " + error.message );
  });   

Relations (Retrieve)

When a data object or a collection of objects is retrieved using the client API, Backendless does not automatically include related objects into the returned object hierarchy. The reason for this is it would make it very easy to load a lot of unnecessary data which could impact application's performance. There are multiple ways to retrieve related data:

Relations auto-load - a mechanism built-into Backendless Console for configuring specific relations to be included into responses with the parent objects.
Single-step relations retrieval - initializing and including related objects into the response when running a find query for parent objects.
Two-step relations retrieval - a process of retrieving relations where the first step is to load the parent object and the second step is to load specific relations for the given parent.
Loading with relations depth - retrieving related objects by specifying the depth of the object hierarchy
Relation paging API - enables "chunked" (or paged) retrieval of related objects
Inverted Retrieval - loading related child objects by condition applied to parent - load related objects using a search query where the condition applies to the parent object properties.

Auto Load

The auto-load feature is designed to be used for quick prototyping. One of its benefits is reduction of client-side code. However, for production quality apps, it is not recommended to use the auto-load relation retrieval as it can greatly impact performance.

By default when an object is retrieved from Backendless using any of the find APIs (basic or advanced), its related objects are not included into the response, unless explicitly referenced in the request. This behavior can be easily modified using Backendless Console:

auto-load

For any two tables A and B where A has a relationship column linking it to B, the console includes the "auto load" checkbox for the relationship column. Selecting the checkbox instructs Backendless to return a paged set of related B objects when the parent instance of A is retrieved through an API call. For example, in the image above, the shown table has a one-to-many "address" relationship with the Address table. When the "auto load" checkbox in the "address" column is selected, a paged set of the related Address objects will be included into the response for a find query for the shown table.

You can disable the auto-load option by setting the relation depth parameter to 0 in an API call.

Single Step Retrieval

Single Step Retrieval loads only a partial set of the related objects (default size of the retrieved related collection is 10). To load additional related objects, use the Relation Paging API.

This approach allows retrieval of a partial set of the related objects along with the parent object in a single findor findById request. Each relationship property (column) must be uniquely identified by name using the API documented below.

 

Retrieving a single object with relations using findById:

Non-blocking call:
Backendless.Data.of( "TABLE-NAME" ).findById( {objectId:"XXXX-XXXX-XXXX-XXXX",
                                               loadRelations:"relationA,relationB" })
 .then( function( result ) {
  })
 .catch( function( error ) {
  });
Blocking call:
var obj = Backendless.Data.of( "TABLE-NAME" ).findById( {objectId:"XXXX-XXXX-XXXX-XXXX",
                                                         loadRelations:"relationA,relationB" } );
Non-blocking call:
Backendless.Data.of( DataTypeX ).findById( {objectId:"XXXX-XXXX-XXXX-XXXX",
                                            loadRelations:"relationA,relationB" })
 .then( function( result ) {
  })
 .catch( function( error ) {
  });
Blocking call:
var obj = Backendless.Data.of( DataTypeX ).findById( {objectId:"XXXX-XXXX-XXXX-XXXX",
                                                      loadRelations:"relationA,relationB" } );

 

Retrieving a collection of objects with relations using find:

var queryBuilder = Backendless.DataQueryBuilder.create();
queryBuilder.setRelated( [ "RELATED-PROPERTY-NAME", 
                           "RELATED-PROPERTY-NAME.RELATION-OF-RELATION" ] );

Then load data using the constructed queryBuilder object with:

Non-blocking call:
Backendless.Data.of( "TABLE-NAME" ).find( queryBuilder )
 .then( function( objectCollection ) {
  })
 .catch( function( error ) {
  });
Blocking call:
var objectCollection = Backendless.Data.of( "TABLE-NAME" ).findSync( queryBuilder );
Non-blocking call:
Backendless.Data.of( DataTypeX ).find( queryBuilder )
 .then( function( objectCollection ) {
  })
 .catch( function( error ) {
  });
Blocking call:
var objectCollection = Backendless.Data.of( DataTypeX ).findSync( queryBuilder );

where

RELATED-PROPERTY-NAME - name of a related property to load. For example, if table Person has a relation "homeAddress" pointing to an object in the Address table, the value would be "homeAddress". The syntax allows to add relations of relations. For example, if the same Address table has a relation "country" pointing to the Country table, then "homeAddress.country" would instruct the related Country object to be loaded as well.
queryBuilder - an instance of the Backendless.DataQueryBuilder class. The class is used to identify related properties for relation retrieval.
TABLE-NAME - Name of the table where the data should be retrieved from
DataTypeX - Reference to a class/function which identifies a table where the data should be loaded from.

 

Two-step Retrieval

Two Step Retrieval loads only a partial set of the related objects (default size of the retrieved related collection is 10). To load additional related objects, use the Relation Paging API.

With this approach a collection of related objects for a specific relation property in a parent object is retrieved from the server. The client application must know parent object's objectId. The API loads a collection of related children for one property at a time. Child objects retrieved in paged sets, see the Relation Paging API sectionfor additional details.

 

Suppose the Person table has a one-to-many relationship column friends pointing to the Users table. The code below retrieves related BackendlessUser objects  for a specific Person object:

Prepare Backendless.LoadRelationsQueryBuilder:
var loadRelationsQueryBuilder; 
loadRelationsQueryBuilder = Backendless.LoadRelationsQueryBuilder.create();
loadRelationsQueryBuilder.setRelationName( "friends" );
Blocking call:
var parentObjectId = // removed for brevity
var friendsArray;
friendsArray = Backendless.Data.of( "Person" ).loadRelationsSync( 
                     parentObjectId, 
                     loadRelationsQueryBuilder );
friendsArray.forEach( function( friend ) {
  console.log( friend.email );
});
Non-blocking call:
var parentObjectId = // removed for brevity
Backendless.Data.of( "Person" ).loadRelations( 
        parentObjectId,
        loadRelationsQueryBuilder )
  .then( function( friendsArray ) {
      friendsArray.forEach( function( friend ) {
         console.log( friend.email );
      });
   })
  .catch( function( error ) {
    console.log( "server reported an error - " + error.message );
  });
Prepare Backendless.LoadRelationsQueryBuilder:
var loadRelationsQueryBuilder; 
loadRelationsQueryBuilder = Backendless.LoadRelationsQueryBuilder.create();
loadRelationsQueryBuilder.setRelationName( "friends" );
Blocking call:
var parentObjectId = // removed for brevity
var friendsArray;
friendsArray = Backendless.Data.of( Person ).loadRelationsSync( 
                     parentObjectId, 
                     loadRelationsQueryBuilder );
friendsArray.forEach( function( friend ) {
  console.log( friend.email );
});
Non-blocking call:
var parentObjectId = // removed for brevity
Backendless.Data.of( Person ).loadRelations( 
        parentObjectId,
        loadRelationsQueryBuilder )
  .then( function( friendsArray ) {
      friendsArray.forEach( function( friend ) {
         console.log( friend.email );
      });
   })
  .catch( function( error ) {
    console.log( "server reported an error - " + error.message );
  });

 

Retrieval with Relation Depth

The Data Service API supports a mechanism for loading related objects without identifying each by its name. Instead, the API includes a parameter which specifies the "depth" of the relations to include into the response. Consider the following diagram:

relation-depth

The diagram shows a hierarchy for class structure - the Order class has two relations: with OrderItem and Customer classes. Each in turn has a relation to the Manufacturer and Address classes. When an instance or a collection of Order objects is retrieved from Backendless, the API may include a parameter specifying the depth of relations to include into the response. If the relation depth is 1, then all related instances of OrderItem and Customer will be included into each Order object. If the relation depth is 2, then not only OrderItem and Customer instances will be included, but the corresponding Manufacturer and Address objects as well.

Loading relations with relation depth retrieves only a partial set of the related objects (default size of the retrieved related collection is 10). To load additional related objects, use the Relation Paging API.

API methods supporting relations depth

Non-blocking methods:
Backendless.Data.of( "TABLE-NAME" ).findFirst( relationsDepth )
 .then( function( firstObjectWithRelations ) {
  })
 .catch( function( error ) {
  });
  
Backendless.Data.of( "TABLE-NAME" ).findLast( relationsDepth )
 .then( function( lastObjectWithRelations ) {
  })
 .catch( function( error ) {
  });
  
Backendless.Data.of( "TABLE-NAME" ).findById( objectId, relationsDepth )
 .then( function( firstObjectWithRelations ) {
  })
 .catch( function( error ) {
  });
  
Backendless.Data.of( "TABLE-NAME" ).find( dataQueryBuilder )
 .then( function( objectsWithRelations ) {
  })
 .catch( function( error ) {
  });
Blocking methods:
Backendless.Data.of( "TABLE-NAME" ).findFirstSync( relationsDepth );
Backendless.Data.of( "TABLE-NAME" ).findLastSync( relationsDepth );
Backendless.Data.of( "TABLE-NAME" ).findByIdSync( objectId, relationsDepth );
Backendless.Data.of( "TABLE-NAME" ).findSync( dataQueryBuilder );
Non-blocking methods:
Backendless.Data.of( ClassFunctionRef ).findFirst( relationsDepth )
 .then( function( firstObject ) {
  })
 .catch( function( error ) {
  });
Backendless.Data.of( ClassFunctionRef ).findLast( relationsDepth )
 .then( function( lastObject ) {
  })
 .catch( function( error ) {
  });
Backendless.Data.of( ClassFunctionRef ).findById( objectId, relationsDepth )
 .then( function( objectWithId ) {
  })
 .catch( function( error ) {
  });
Backendless.Data.of( ClassFunctionRef ).find( dataQueryBuilder )
 .then( function( objectArray ) {
  })
 .catch( function( error ) {
  });
Blocking methods:
var firstObject = Backendless.Data.of( ClassFunctionRef ).findFirstSync( relationsDepth );
var lastObject = Backendless.Data.of( ClassFunctionRef ).findLastSync( relationsDepth );
var objectWithId = Backendless.Data.of( ClassFunctionRef ).findByIdSync( objectId, relationsDepth );
var objectArray = Backendless.Data.of( ClassFunctionRef ).findSync( dataQueryBuilder );

Example:

var queryBuilder = Backendless.DataQueryBuilder.create();
queryOptions.setRelationsDepth( 2 );

// non-blocking call
Backendless.Data.of( "Foo" ).find( queryBuilder )
 .then( function( objectArray ) {
  })
 .catch( function( error ) {
  });
  
// blocking call
var objectArray = Backendless.Data.of( "Foo" ).findSync( queryBuilder );
// define the constructor function first
function Foo {
  // Foo properties are declared here
}

var queryBuilder = Backendless.DataQueryBuilder.create();
queryOptions.setRelationsDepth( 2 );

// non-blocking call
Backendless.Data.of( Foo ).find( queryBuilder )
 .then( function( objectArray ) {
  })
 .catch( function( error ) {
  });
  
// blocking call
var objectArray = Backendless.Data.of( Foo ).find( queryBuilder );


 

Relation Paging

API operations described above provide a way to retrieve a limited (initial) set of related data. In most real world scenarios, there may be more related objects than returned by Single Step, Two Step or Relation Depth Retrieval APIs. To obtain a complete set of related objects Backendless supports Relation Paging API described below.

1. To understand the concept of paged data retrieval, see the Data retrieval with Paging section of the documentation.
2. When working with paging, it may be important to know the total size of the related collection. See the Get Object Count section for details on how to get the related objects count.

JS applications must use the Backendless.LoadRelationsQueryBuilder class which facilitates retrieval of related objects for a parent object. The class is responsible for maintaining the page size and offset parameters used in the paging process.

 

Before an instance of Backendless.LoadRelationsQueryBuilder can be used in a data retrieval request, it must be initialized as shown below. Notice the setRelationName call which identifies a related column for which the related objects will be loaded:

var loadRelationsQueryBuilder = Backendless.LoadRelationsQueryBuilder.create();
loadRelationsQueryBuilder.setRelationName( "NAME-OF-RELATED-COLUMN" );
var loadRelationsQueryBuilder = Backendless.LoadRelationsQueryBuilder.of( CHILDCLASS );
loadRelationsQueryBuilder.setRelationName( "NAME-OF-RELATED-COLUMN" );

where:

CHILDCLASS - Used in the "Custom Class/Function" approach only. A JS function/class which identifies the related table.
NAME-OF-RELATED-COLUMN - Name of the related column in the parent table for which the related objects will be retrieved.

 

Once a query builder object is created, the following API can be used to retrieve a page of related objects:

// non-blocking API
Backendless.Data.of( "PARENT-TABLE-NAME" ).loadRelations( 
                            parentObjectId,
                            queryBuilder )
 .then( function( relatedObjectsArray ) {
  })
 .catch( function( error ) {
  });

// blocking API
var relatedObjectsArray = Backendless.Data.of( "PARENT-TABLE-NAME" ).loadRelationsSync( 
                            parentObjectId,
                            queryBuilder );
// non-blocking API
Backendless.Data.of( PARENT-CLASS ).loadRelations( queryBuilder )
 .then( function( relatedChildrenArray ) {
  })
 .catch( function( error ) {
 });

// blocking API
var relatedObjectsArray = Backendless.Data.of( PARENT-CLASS ).loadRelationsSync( queryBuilder );

where:

parentObjectId -  id of the object for which the related objects will be retrieved.
PARENT-TABLE-NAME - name of the table which contains the parent object identified by parentObjectId.
queryBuilder - Backendless.LoadRelationsQueryBuilder initialized as shown above. Used in subsequent calls to request additional pages of related data objects.

 

The LoadRelationsQueryBuilder class contains methods which recalculate offset in order to obtain next, previous or a specific page. After loadRelationsQueryBuilder is modified, call the API shown above to get next, previous or a specific page of data.

var loadRelationsQueryBuilder = // initialized as shown above

// sets page size which will be used in subsequent related object retrieval.
loadRelationsQueryBuilder.setPageSize( pageSize );

// calculates offset of the next page based on set page size. 
loadRelationsQueryBuilder.prepareNextPage();

// calculates offset of the previous page based on set page size
loadRelationsQueryBuilder.preparePreviousPage();

// sets offset explicitly
loadRelationsQueryBuilder.setOffset( offset );

Inverted Relation Retrieval

Backendless supports a special query syntax for loading a subset of child objects for a specific parent. Consider the following table schemas:

 

PhoneBook table:

phonebook-table-schema

 

Contact table:

contact-table-schema

 

Address table:

address-table-schema

These tables will be used to demonstrate how to query Backendless for conditional related object retrieval.

The untyped objects approach does not require you defining classes for the objects stored in Backendless. Instead your code can use plain JS objects to to store and retrieve objects in/from Backendless.
Consider the following class definitions for the entities from the diagram: PhoneBook class:
function PhoneBook( args ) {
    args = args || {};
    this.___class = 'PhoneBook';
    this.owner = args.owner || {}; // Contact
    this.contacts = args.contacts || null; // collection of Contacts
}
Contact class:
function Contact(args) {
    args = args || {};
    this.___class = 'Contact';
    this.name = args.name || "";
    this.age = args.age || "";
    this.phone = args.phone || "";
    this.title = args.title || "";
    this.address = args.address || {};
}
Address class:
function Address(args) {
    args = args || {};
    this.___class = 'Address';
    this.street = args.street || "";
    this.city = args.city || "";
    this.state = args.state || "";
}

 

The general structure of a whereClause query to load a collection of child objects for a specific parent object is:

ParentTableName[ relatedColumnName ].parentColumnName COLUMN-VALUE-CONDITION

Both columns relatedColumnName and parentColumnName must be declared in a table with name of ParentTableName. The relatedColumnName must be a relation column. The table relatedColumnName points to is the table where the objects must be loaded from.  The examples below demonstrate the usage of this syntax:

Find all contacts in a city for a specific phone book:

// assume a phone book is created with one or more contacts. 
// See the relevant samples above which demonstrate how that can be done
var contactStorage = Backendless.Data.of( "Contact" );
var JohnsPhoneBookID = JohnsPhoneBook[ "objectId" ].split( '.' )[0];
var query = {condition: "PhoneBook[contacts].objectId='" + 
                        JohnsPhoneBookID + 
                        "' and address.city='Denver'"};
var contactsFromDenver = contactStorage.find( query );
// assume a phone book is created with one or more contacts. 
// See the relevant samples above which demonstrate how that can be done
var contactStorage = Backendless.Data.of( Contact );
var JohnsPhoneBookID = JohnsPhoneBook[ "objectId" ].split( '.' )[0];
var query = {condition: "PhoneBook[contacts].objectId='" + 
                        JohnsPhoneBookID + 
                        "' and address.city='Denver'"};
var contactsFromDenver = contactStorage.find( query );

Find all contacts for the specific phone book where the city name contains letter 'a':

// assume a phone book is created with one or more contacts. 
// See the relevant samples above which demonstrate how that 
// can be doneuse any of the samples above

var contactStorage = Backendless.Persistence.of( "Contact" );
var JohnsPhoneBookID = JohnsPhoneBook[ "objectId" ].split( '.' )[0];
var query = {condition: "PhoneBook[contacts].objectId='" + 
                         JohnsPhoneBookID + 
                         "' and address.city like '%a%'"};
var contactsFromDenver = contactStorage.find( query );
// assume a phone book is created with one or more contacts. 
// See the relevant samples above which demonstrate how that 
// can be doneuse any of the samples above

var contactStorage = Backendless.Persistence.of( Contact );
var JohnsPhoneBookID = JohnsPhoneBook[ "objectId" ].split( '.' )[0];
var query = {condition: "PhoneBook[contacts].objectId='" + 
                         JohnsPhoneBookID + 
                         "' and address.city like '%a%'"};
var contactsFromDenver = contactStorage.find( query );

Find all contacts where age is greater than 20 for a specific phone book:

// assume a phone book is created with one or more contacts. 
// See the relevant samples above which demonstrate how that 
// can be doneuse any of the samples above

var contactStorage = Backendless.Persistence.of( "Contact" );
var JohnsPhoneBookID = JohnsPhoneBook[ "objectId" ].split( '.' )[0];
var query = {condition: "PhoneBook[contacts].objectId='" + 
                         JohnsPhoneBookID + 
                         "' and age > 20"};
var contactsFromDenver = contactStorage.find( query );
// assume a phone book is created with one or more contacts. 
// See the relevant samples above which demonstrate how that 
// can be doneuse any of the samples above

var contactStorage = Backendless.Persistence.of( Contact );
var JohnsPhoneBookID = JohnsPhoneBook[ "objectId" ].split( '.' )[0];
var query = {condition: "PhoneBook[contacts].objectId='" + 
                         JohnsPhoneBookID + 
                         "' and age > 20"};
var contactsFromDenver = contactStorage.find( query );

Find all contacts for a specific phone book where age is within the specified range:

// assume a phone book is created with one or more contacts. 
// See the relevant samples above which demonstrate how that 
// can be doneuse any of the samples above

var contactStorage = Backendless.Persistence.of( "Contact" );
var JohnsPhoneBookID = JohnsPhoneBook[ "objectId" ].split( '.' )[0];
var query = {condition: "PhoneBook[contacts].objectId='" + 
                         JohnsPhoneBookID + 
                         "' and age >= 21 and age <= 30"};
var contactsFromDenver = contactStorage.find( query );
// assume a phone book is created with one or more contacts. 
// See the relevant samples above which demonstrate how that 
// can be doneuse any of the samples above

var contactStorage = Backendless.Persistence.of( Contact );
var JohnsPhoneBookID = JohnsPhoneBook[ "objectId" ].split( '.' )[0];
var query = {condition: "PhoneBook[contacts].objectId='" + 
                         JohnsPhoneBookID + 
                         "' and age >= 21 and age <= 30"};
var contactsFromDenver = contactStorage.find( query );

Find all contacts for a specific phone book where age is greater than 20 and the city is Tokyo:

// assume a phone book is created with one or more contacts. 
// See the relevant samples above which demonstrate how that 
// can be doneuse any of the samples above

var contactStorage = Backendless.Persistence.of( "Contact" );
var JohnsPhoneBookID = JohnsPhoneBook[ "objectId" ].split( '.' )[0];
var query = {condition: "PhoneBook[contacts].objectId='" + 
                        JohnsPhoneBookID + 
                        "' and age > 20 and address.city = 'Tokyo'"};
var contactsFromDenver = contactStorage.find( query );
// assume a phone book is created with one or more contacts. 
// See the relevant samples above which demonstrate how that 
// can be doneuse any of the samples above

var contactStorage = Backendless.Persistence.of( Contact );
var JohnsPhoneBookID = JohnsPhoneBook[ "objectId" ].split( '.' )[0];
var query = {condition: "PhoneBook[contacts].objectId='" + 
                        JohnsPhoneBookID + 
                        "' and age > 20 and address.city = 'Tokyo'"};
var contactsFromDenver = contactStorage.find( query );

Relations with Geo Points

Backendless Geo Service manages application's geo location data and provides APIs to work with Geo points. Backendless supports integration between data objects managed by Data Service and geo points for the scenarios when a logical connection between the two entity types must exist in an application. For instance, in a taxi ordering application a data object may represent a taxi car, while a geo point represents its location on the map. Linking the two entity types together provides great benefits such as retrieving both objects at once and managing as a consistent, cohesive object hierarchy.

The Data-to-Geo integration is implemented through object relations. Data table schema may declare a table column with a special data type - "GeoPoint Relationship". As a result, the data objects in the table may contain a reference to one or more GeoPoints. When a data object with a related GeoPoint is saved, Backendless persists information about both the data object and the geo point in the corresponding persistent systems and sets up the relationship. Likewise, when a data object is retrieved by using the API, any related geo points can be retrieved using the same principle for loading data relations. The data-to-geo relation is bidirectional, it means a geo point may reference a data object in its metadata. You can learn more about it in the Relations with Data Objects section of the Geolocation documentation.

The relationship between a data object and a geo point (or a collection of) can be established by using either the "code first" or the "schema first" approaches. With the former, the relationship is determined by the data structure persisted with the API. If a data object references a GeoPoint (or a collection of) in one of its properties, Backendless interprets it as a relation and, as a result, will create a relation column in the data table schema. With the latter ("schema first") approach, application developer can declare a relationship in the data table schema first. In either one of these approaches, once a relationship is declared, data objects and geo points may be linked together by using the Backendless console as well.

 

This chapter consists of the following sections:

Declaring a Data-to-Geo Relationship in Table Schema

Linking a Data Object with Geo Points

Update/Delete Relations

Deleting Relation Column in Table Schema

Establishing Relations with Geo Points via API

Declaring a Data-to-Geo Relationship in Table Schema


To declare a relationship in a data table schema:

1. Select a table where a relation column should be declared.
2. Click the Schema menu.
3. Click the New button. Enter a name for the new column in the Name field. This column will represent a data-to-geo relationship.
4. Select the Geopoint Relationship option from the Type drop-down list.
geo-point-relationship-type
5. Select the constraints and/or cardinality of the relation from the corresponding drop-down menus.
 
Constraints:
There are two available constraints: "Not Null (Required)" and "Unique Value". The former (NN) establishes the column as required. It means when a new object is created or an existing one is updated, server will be expecting a value for the column. The latter constraint (UQ) will enforce a rule that the column must contain a unique value, thus no two objects will be able to contain the same GeoPoint.
 
Cardinality:
The one-to-one relation means that a table's object can be linked with only one geo point, while the one-to-many relation means that a table's object can be linked with multiple geo points.
location-column
6. Click the CREATE button to save the changes.

Linking a Data Object with Geo Points


Once a data-to-geo relationship column is declared, data objects from the table can be linked to geo point(s) as described below:

1. Click the name of the table containing an object you want to link with a geo point.
2. Table columns representing the data-to-geo relationships are identified as "GEOPOINT relationship" in the header row. The cardinality of the relation is visualized as one red line for the one-to-one relations and three red lines for the one-to-many relations:
geopoint-column.zoom70
3. To create a relationship between an object and a geopoint, click the plus icon next in the cell for a GEOPOINT column.
plus-icon-for-geopoint.zoom70
4. The Set Related GeoPoint pop-up window will display the list of the geo points. Use the Geo Category drop-down list to select a geo category from which the points should be displayed. Additionally, you can use the search bar to locate a geopoint by its metadata:
geopoint-search.zoom80
5. If you declared a one-to-one relation for a table the object belongs to, you will be able to link this object with only one geo point (by the means of a radio button). If it is a one-to-many relationship, the interface uses check boxes, which allow for multiple selection. Click a radio-button or select check-boxes next to the geo points which you want to link with the data object.
6. Click the ADD RELATION button to save the changes.

Once a relation is established, it is shown in the data browser as a hyperlink. The hyperlink for the one-to-one relations displays the coordinates of the related geo point. For the one-to-many relations the link says "multiple Geopoints". In both cases, the link opens the Geolocation screen of the console which displays the related geo point(s). Additionally, the link has a mouse-over preview for the related geopoints:

geopoint-preview

Update/Delete Relations


To update a data-to-geo relation, use the same plus icon that opened the popup to create a geopoint relation. The popup tracks the selection changes and allows update or deletion of the relation.

Deleting Relation Column in Table Schema


A data-to-geo relationship can be removed at the schema level. When a relationship column is removed, the "links" between the corresponding data objects and geopoints are deleted, however, it does not remove the objects themselves.

To delete a relationship definition between a data table and the geo points:

1. Click the name of the table which contains a "GeoPoint relationship" column you need to remove.
2. Click the Schema menu
3. Click the check-box next to the column you need to delete.
4. Click the Delete menu.
delete-geopoint-relation-column

Establishing Relations with Geo Points via API


Creating a relationship between a data object and a geo point (or a collection of) uses the same API as saving a data object with a related entity. In the case of data-to-geo relations, the related entity is a geopoint or a collection of geopoints. Consider the example that below saves a data object with a related geopoint. The geopoint is also persisted in the Geo Service:

 

The example below demonstrates how to link a taxi (a data object) with location (a geo point). First, declare the TaxiCab class:

The "Untyped Objects" approach does not require you defining classes for the objects stored in Backendless. Instead your code can use plain JS objects to to store and retrieve objects in/from Backendless.
function TaxiCab (args) {
    args = args || {};
    this.CarMake = args.CarMake || null;
    this.CarModel = args.CarModel || null;
    this.Location = new GeoPoint();
    this.PreviousDropOffs = [];
}

To set a one-to-one or a one-to-may relation:

var point = new Backendless.GeoPoint();
point.latitude = 40.7148;
point.longitude = -74.0059;
point.categories = [ "taxi" ];
point.metadata = { service_area : "NYC" };

// one-to-many relation between a data object and geo points
var droppOff1 = new Backendless.GeoPoint();
droppOff1.latitude = 40.757977;
droppOff1.longitude = -73.98557;
droppOff1.metadata = { name:"Times Square" };
droppOff1.categories = [ "DropOffs" ];

var droppOff2 = new Backendless.GeoPoint();
droppOff2.latitude = 40.748379;
droppOff2.longitude = -73.985565;
droppOff2.metadata = { name: "Empire State Building" };
droppOff2.categories = [ "DropOffs" ];

var taxi = { carmake:"Toyota", carmodel:"Prius" };

// link one geopoint with the data object
taxi.location = point;

// link several geopoints with the data object
taxi.previousDropOffs = [ droppOff1, droppOff2 ];

Backendless.Data.of( "Taxi" ).save( taxi )
 .then( function( savedObject ) {
   console.log( "taxi object has been saved" );
  })
 .catch( function( error ) {
   console.log( "error - " + error.message );
  });
var point = new Backendless.GeoPoint();
point.latitude = 40.7148;
point.longitude = -74.0059;
point.categories = [ "taxi" ];
point.metadata = { service_area : "NYC" };

// one-to-many relation between a data object and geo points
var droppOff1 = new Backendless.GeoPoint();
droppOff1.latitude = 40.757977;
droppOff1.longitude = -73.98557;
droppOff1.metadata = { name:"Times Square" };
droppOff1.categories = [ "DropOffs" ];

var droppOff2 = new Backendless.GeoPoint();
droppOff2.latitude = 40.748379;
droppOff2.longitude = -73.985565;
droppOff2.metadata = { name: "Empire State Building" };
droppOff2.categories = [ "DropOffs" ];

var taxi = new Taxi();
taxi.carmake = "Toyota";
taxi.carmodel = "Prius";

// link one geopoint with the data object
taxi.location = point;

// link several geopoints with the data object
taxi.previousDropOffs = [ droppOff1, droppOff2 ];

Backendless.Data.of( Taxi ).save( taxi )
 .then( function( savedObject ) {
   console.log( "taxi object has been saved" );
  })
 .catch( function( error ) {
   console.log( "error - " + error.message );
  });

After you run the code, you will see the following data object and geopoints created in the application:

Data object:

taxi-data-object.zoom70        

Geopoints:

taxi-geo-points.zoom70

Data Security

Data Service supports a very flexible security mechanism for restricting access to objects stored in Backendless. Security permissions apply to users and roles. A permission can either grant or reject an operation for a particular asset. In the context of Data Service, the asset is an object which your app can retrieve, update or delete. Permissions can be granted or rejected globally, where they apply to all tables and all objects in the data store. Additionally, every table may have its own permission matrix and owner policy - a special instruction whether object owners can or cannot retrieve/update/delete the objects they 'own'. Finally, every object has its own Access Control List (ACL) which is a matrix of permissions for the operations applicable specifically to the object:

backendless-security-components

The security system is multi-layered. For any API call the system goes through several layers where each can trim the scope of the operations. The layered order of the decision making is important and consists of the following points of validation:

1. ObjectACL for the user who makes the call
2. ObjectACL for user-defined roles assigned to the user who makes the call.
3. Table permissions for the User account
4. Table permissions for the user-defined roles
5. Owner Policy
6. ObjectACL for system roles
7. Table permissions for system-level roles
8. Global user-defined roles
9. Global system roles

Where:

"User-defined roles" - roles created by the application developer.
"System roles" - roles built into Backendless (AuthenticatedUser, NonAuthenticatedUser, SocialUser, ServerCodeUser, etc).

 

Consider the following guide which illustrates the decision making process:

Backend receives an API request to load data from a table (the Find operation). For the sake of the example, user's identity is "bob@backendless.com" and the user belongs to a custom role called "MyRole".
 
All objects in the table become candidates for the retrieval. Backendless goes through the security permissions layers to determine which objects must be included into the response.
 

1. [LAYER 1] ObjectACL for the user who makes the call.
Backendless checks if there are any restrictions for the user account at the object level. Any object in the collection with ACL which rejects access to the user is excluded from the result. To see or modify the permissions for a particular object, click the 'lock' icon in the ACL column in the data browser in management console to see and manage Object ACL permissions for a specific object.
object-acl-access.zoom80
 
2. [LAYER 2] ObjectACL for user-defined roles assigned to the user who makes the call.
This is the same check as the one above, except Backendless looks into the permissions for the roles defined by the application developer. If the user who made the call belongs to any of the custom roles, Backendless checks if these roles are allowed to perform the current operation for every object in the collection. In the screenshot below, only the "MyRole" role will be checked in this step, since this is the only custom role in the application:
object-acl-custom-role.zoom80
 
3. [LAYER 3] Table permissions for the User account.
Every table in Backendless may have its own set of permissions for users and roles. At this point Backendless checks if the currently logged in user is allowed to run the current operation. For example, if the Find operation is denied for the user, no objects would be returned. To see the permissions for a user account in Backendless Console, select a table and click PERMISSIONS, then USER PERMISSIONS menu:
table-permissions-for-users.zoom80

 

4. [LAYER 4] Table permissions for the user-defined roles.
This step is identical to the one described above with the exception that is checks custom roles for the table. Since this guide reviews the decision making process for the Find operation, Backendless checks the column for Find. If any of the custom roles which the user belongs to deny access, the operation is rejected and no data is returned.
custom-user-roles-for-table.zoom80
 
5. [LAYER 5] Owner Policy.
When a new object is created in Backendless, the system automatically links it with the account of the user that made the call to save the object. You can see that information in the 'ownerId' column in any of your tables in the data browser.  With the association between objects and users (owners), Backendless provides a way to control whether users can get access to the data they created. This is done through a concept we call 'Owner Policy'. To navigate to Owner Policy, select a table in the data browser and click the PERMISSIONS menu. Click the OWNER POLICY menu item.
Granting a permission for an operation in Owner Policy, guarantees that the objects owned by the current user will be allowed access for the specified operations. Denying a permission, takes out the 'owned' objects from the collection of candidate objects to return.
 
6. [LAYER 6] Object ACL for system roles.
This check is identical to step 2 ([LAYER 2] Object ACL for custom roles). The difference is the system roles cover larger groups of users. For example, this step would make possible to restrict access to specific objects for all authenticated (or not authenticated) users, yet the object would be returned with a query made by the object's owner if the Owner Policy (previous step) grants access.  An exception to this rule are API calls from business logic. In that case, if there is no authenticated user in the context of the call, Backendless assigns only ServerCodeUser role. The NotAuthenticatedUser role is not assigned and thus is not checked.
 
7. [LAYER 7] Table permissions for system roles.
Identical to step 4 (table permissions for custom roles), this checks if any of the system roles reject the operation at the table level.
 
8. [LAYER 8] Global custom roles.
Global policy applies to all tables and objects. By default all table level permissions inherit from the global policy. You can configure in the console at: Users > Security Roles. Create a new role and click it to configure the permission matrix:
custom-role-global-matrix.zoom70

Messaging API

Overview

Data Messaging is an essential function of mobile and desktop applications. It can be used for a multitude of functions including chat or private messaging, system update broadcast, maintaining game scores, etc. The Backendless Messaging Service provides API and software infrastructure enabling publish-subscribe message exchange pattern and mobile push notifications. The service consists of the following core concepts: channels, publishers, subscribers and registered devices:

channel        -  a logical medium "transporting" the messages.
publisher        - a program using the Publishing API to send messages to a channel.
subscriber        - a program using the Subscription API to receive messages from a channel.
registered device        - a mobile device registered with a Backendless channel to receive push notifications.

 

Publish-Subscribe Messaging

With the publish-subscribe pattern, one part of the code (or an entire application) can subscribe to receive messages and another publishes messages. A message can be any data - Backendless supports messages of primitive or complex data types. To enable publish-subscribe messaging, Backendless supports the concept of a channel. Subscriptions are "attached" to a channel (or multiple channels) and messages are published into a channel. By default Backendless sends all messages published into a channel to all the channel's subscribers. However, a subscription can include message filters, in this case Backendless delivers only the messages matching the filter.

 

Push Notifications

A message published to a channel can be tagged as a push notification, thus triggering the logic for distributing it to the registered devices. Backendless supports push notifications for iOS, Android and Windows Phone devices. Messages published as push notifications can target either a specific subscriber (as a device) or a group of subscribers. Subscribers can be grouped by operating system (for example, a message sent to all registered iOS devices) or as a collection of individual registrations. The Backendless messaging API supports different types of push notifications - badge updates, alerts, etc.

Sending Email

Backendless provides API for email delivery on behalf of your application. Before the API can be used, the Backendless backend must be configured with your own SMTP server information. This is an important requirement as the API will not work if the Manage > App Settings > Email Settings section in Backendless Console contains default values.

 

Configuration


To configure the backend:

 

1. Login to Backendless Console.
2. Select an app.
3. Click Manage, then scroll down to Email Settings on the App Settings screen.
4. Fill out the form with the SMTP server information
email-settings.zoom50

 
where:

SMTP Server - Hostname or public IP address of the server where the SMTP server is running.
Port - The port number the SMTP server accepts requests on.
From - The Name which will appear in the From field of the sent out emails.
User ID - The user id or email address for the SMTP server connection authentication
Password - The password for the SMTP server connection authentication.
Security - Choose between SSL or TLS connection.

 

Make sure to click Test before saving any configuration changes. The Reset button discards any unsaved changes.

 

Sending Email API


Delivers an email message using current server-side email settings to the recipient specified in the API call.

 

Non-blocking method:

Backendless.Messaging.sendEmail( subject, bodyParts, recipients, attachments );

Blocking method:

Backendless.Messaging.sendEmailSync( subject, 
                                     bodyParts, 
                                     recipients, 
                                     attachments );

where:

subject - email message subject.
bodyParts - an instance of Bodyparts,  which contains either plain text and/or HTML version of the message body.
recipients - an array of email addressed to deliver the email message to.
attachments - an array of file paths for the file entries from the Backendless File Service. Referenced files will be attached to the email message. The path is calculated from the root of the file system (as it is seen in File Service browser in Backendless console) without the leading slash. For example, if file agreement.txt is located at /documents/legal/, then the path in the API call must be "documents/legal/agreement.txt".

 

Example:

// prepare message bodies (plain and html) and attachment
var bodyParts = new Backendless.Bodyparts();
bodyParts.textmessage = "Check out this awesome code generation result";
bodyParts.htmlmessage = "Check out this <b>awesome</b> code generation result";
var attachments = ["backendless-codegen.zip" ];

// non-blocking call
Backendless.Messaging.sendEmail( "Email from Backendless", 
                                 bodyParts, 
                                 [ "james.bond@mi6.uk.co" ], 
                                 attachments )
 .then( function( response ) {
   console.log( "message has been sent" );
  })
 .catch( function( error ) {
   console.log( "error " + error.message );
  })
 
// same thing, but with blocking call
Backendless.Messaging.sendEmailSync( "Email from Backendless", 
                                     bodyParts, 
                                     [ "james.bond@mi6.uk.co" ], 
                                     attachments );
console.log( "message has been sent" );

 

Push Notifications

Push Notification Setup (Android)

Backendless can deliver published messages as push notifications to Android devices. Additionally, Backendless Console can be used to publish push notifications. In order to deliver a push notification to Android, the backend must be configured with Google API Key:

2. Click the Get started. Pick a platform button.
server-key-step1
3. Click Enable services for my Android App:
server-key-step2
4. Select your Android app from the list. if the app is not in the list, enter the name of the app, it will be automatically registered in the Google Developer Console.
5. Enter Android package name of your app.
6. Click Continue to Choose and configure services.
server-key-step3
7. Click Cloud Messaging and then click Enable Google Cloud Messaging:
server-key-step4
8. Google generates API key and sender ID. Copy Server API Key:
generated-ids
9. Open Backendless Console and select your application.
10. Click Manage and scroll down to Mobile Settings.
11. Paste the Google API Key into corresponding field located under the Android Push Notifications label:
adding-android-key.zoom50
12. Click Save. At this point the backend is configured and is ready to publish push notifications to Android devices.

 

Google Project Number (Sender ID)

In your project you should register the device in order to receive or send push notifications. To accomplish this, do the following:

1. Return to Step 8 in the instructions above.
2. Copy Sender ID.
3. Use the Sender ID value in Backendless.Messaging.registerDevice(...) method as GCMSenderID argument. For example:
google-project-number-in-code

Push Notification Setup (iOS)

Setting up your backend to support Push Notifications for iOS requires a few steps, most of which are in Apple Developer Member Center and Keychain Access.  The process consists of the following steps:

 

 

Creating App ID

1. First we are going to create an App ID for the mobile application which will receive Push Notifications. Login to Apple Developer Member Center. Click on "App IDs" in the "Identifiers" section. Use the plus sign "+" button to create a new ID:
create-app-id.zoom60
2. When prompted enter App ID Prefix. Make sure it is descriptive enough so you recognize it later when you return to the Member Center.
3. Select Explicit App ID in the "App ID Suffix" section and enter the same bundle ID which you will be using in the application:
appidsuffix.zoom95
4. In App Services select the services which the application will use and click "continue":
appservices.zoom95
5. Make sure that Push Notifications are enabled and click "submit". This will conclude the App ID creation for the app:
confirm-app-id-creation.zoom90

 

Creating Certificate Request

Push Notifications require a certificate which will be used on a device by the means of a provisioning profile. Also the same certificate (transformed to the Personal Information Exchange - .p12 format) will be used by Backendless to publish Push Notifications. If this makes little sense, do not worry, you will need to perform these steps only ones and then can move on to code and using the APIs.

1. In order to create a certificate a Certificate Signing Request (CSR) must be issued. To create a CSR, open Keychain Access and select Keychain Access >> Certificate Assistant >> Request a Certificate from the main menu:
certificaterequest.zoom80
2. Enter your email address and Common Name (leave the CA Email Address field empty), select "Saved to disk" and click "Continue":
CertificateRequestData
3. Select a directory where to save the file and click Save.

 

Generating an SSL Certificate

The CSR file created in the section above will be used to create an SSL Certificate. That certificate will then be used by Backendless to publish push notifications.

1. Return to Apple Developer Member Center and select "All" under "Certificates". Click the plus button "+" to add a new certificate:
membercenteraddcertificate.zoom60
2. Select certificate type - there are two options Development and Production. For now select "Apple Push Notification service SSL (Sandbox)":
push-cert-sandbox.zoom90
3. Select the App ID created earlier in these instructions:
SelectAppId
4. Next you will see the instructions for generating a CSR which you have already created by now. Click Continue to proceed to the next step.
5. Select the CSR file created and saved to the disk earlier and click Generate:
select-csr-file
6. The certificate is ready now, click "Download" to download it:
download-push-cert.zoom90
7. Add the certificate file to Keychain Access.
8. Open Keychain Access and locate the certificate in the "My Certificates" section:
locate-push-cert-keychain-access.zoom60
9. Right click on the certificate and select the Export option:
export-push-cert.zoom70
10. Save the certificate in the p12 format:
save-cert-p12-format
11. Enter a password for the certificate. Make sure to make a record of the password - you will need to use it later in the instructions when you submit the certificate to Backendless:
enter-password
12. Enter your Mac OS X account password to confirm the action. At this point you have a certificate for Push Notifications.

 

Configuring Backendless App/Backend with the Certificate

Since Backendless provides the actual server-side integration for delivering Push Notifications for your application, it needs to have access to the certificate you created above. The steps below provide the instructions for uploading the certificate into Backendless:

1. Login to Backendless Console and select an application which you will use on the server-side.
2. Click Manage > App Settings. Locate the Mobile Settings section and click the IOS menu.
3. Click the Add Certificate button and upload the .p12 certificate created earlier. Make sure to enter the same password you used when created the certificate. The Channels control lets you select the messaging channels which the certificate will be used with. If you intend to use only one certificate, click the Add channels checkbox.
add-ios-cert
4. Now your Backendless server is ready to publish Push Notifications.

Managing Registrations

Application developers can manage device registrations using the Backendless Console:

1. Login to Backendless Console and select an app where you would like to see the device registrations.
2. Click the Data icon and select the DeviceRegistration table.
devicereg-table.zoom50

 

The table provides a single, unified view of all devices registered to receive push notifications. The table contains important information such as deviceId, deviceToken, a name of the messaging channel (channelName). Since the data about registered devices is stored directly in the Backendless database, it automatically supports the following functions:

Registered devices can be exported/imported into Backendless apps using the export/import functionality of the Data Service.
Data relations between registered devices and other data objects and geopoints (and vice versa)
Devices can be registered using the Data Service API or directly via the console.
SQL (whereClause-based) searches of the devices using the console or the Data Service API.

 

Backendless automatically links registered device with the currently logged in user. Suppose your app registers the device for push notifications. If there is a logged in user at the time when the device is being registered, Backendless creates a relation between the device and the user in the database (via a 1:1 relation):

device-to-user-mapping.zoom70

Publish Push Notifications

Backendless supports push notification delivery to iOS, Android and Windows Phone devices. A device must be registered (see the Device Registration API) in order to receive a push notification. A push notification can be published via API or using the Backendless Console. The content of a push notification consists of a message body and the specialized message headers.

 

Publish a Push Notification from Console

To publish a push notification from Backendless console, navigate to the Messaging screen and click the Devices tab. Make sure there are registered devices, otherwise a push notification has nowhere to go.

1. Select the check boxes for the device(s) you would like to send a notification to. Select Android or iOS tab to enter push notification parameters.
2. Enter parameter values into the corresponding fields.
3. Click the Publish Message button to send a push notification.
messaging-screen-v4.zoom50

 

Publish Push Notification with API

Message publishing API supports the following scenarios:

 

Basic push notification - In the simplest form a push notification is sent to a channel and this to all the devices registered with the channel. The notification message must contain appropriate headers which control the appearance of the notification on the devices. Example.

 

Push notification delivered to devices grouped by the OS - With this type of message delivery push notifications can be sent to all devices of a specific operating system (or a group of).  This includes delivery to Android, iOS or Windows Phone devices or a any combination of. Example.
 
Push notification delivered to specific devices - This delivery type allows push notification delivery to individual devices. Example
 
Delayed publishing - For all scenarios described above a push notification request can be configured to be executed at a later time (thus "delayed publishing"). Messages published with this delivery option can be canceled at any time using the message cancellation API. Example.

 

Push Notification Headers

The headers described below must be added to the "publish options" object or in case of REST clients, they are plain message headers:

Operating System

Headers

Description

 

iOS

"ios-alert-title":value

Sets the title of the push notification message.

"ios-alert-subtitle":value

Sets the sub-title of the push notification message (iOS 10+)

"ios-alert-body":value

Sets the text of the alert message. If the header is not present and the published notification targets the iOS devices, Backendless sets the header to the value of the "message" argument.

"ios-badge":value

Sets the value to update the badge with

"ios-sound":URL string or array of bytes

Sets either a URL for the sound notification to play on the device or an array of bytes for the sound to play.

"ios-content-available":value

Configures a silent-notification. When this key is present, the system wakes up your app in the background and delivers the notification to its app delegate.

Android

"android-ticker-text":value

Sets the text of the ticker showing up at the top of a device's screen when the device receives the notification.

"android-content-title":value

Sets the title of the notification as it is visible in the Android Notification Center

"android-content-text":value

Sets the message of the notification which appears under android-content-title in the Android Notification Center.

Windows Phone

"wp-title":value,
"wp-content":value

Sets the title and the content of a toast notification.

"wp-type":"TILE":
"wp-title" : value,
"wp-backgroundImage" : URL string,
"wp-badge" : number value,
"wp-backTitle" : value,
"wp-backImage" : URL string,
"wp-backContent" : value

Sets the properties for a tile notification.

"wp-type":"RAW",
"wp-raw":XMLString

Sets the properties for a raw notification

Publishes push notification to a channel. Headers must be added to the publishOptions argument. The deliveryOptions argument can be used to:

1. Designate the message as "push notification only" or both a push notification and a pub/sub message.
2. Singlecast publishing - set a list of devices which should be receiving the notification.
3. Broadcast publishing - identify operating systems of the registered devices which should be receiving the notification.
4. Publish the message at a later time (delayed publishing).
5. Specify that the message should be republished multiple times (repeated publishing).

Non-blocking method:

Backendless.Messaging.publish( channelName, 
                               message, 
                               publishOptions, 
                               deliveryOptions )
 .then( function( response ) {
  })
 .catch( function( error ) {
  });

Blocking method:

var response = Backendless.Messaging.publishSync( channelName, 
                                                  message, 
                                                  publishOptions, 
                                                  deliveryOptions )

where:

channelName - name of the channel to publish the message to. If the channel does not exist, Backendless automatically creates it.
message - object to publish. Can be a primitive value, an array or a complex type.
publishOptions - requires argument. An instance of the Backendless.PublishOptions class. Contains publisher ID, message headers and subtopic name.
deliveryOptions - optional argument. An instance of the Backendless.DeliveryOptions class. May specify message delivery policy (push, pub/sub or both), timestamp (in milliseconds) for publishing at the specified time in the future, interval for repeated publishing.

Return value:

An untyped JavaScript object containing message ID and the status of the publishing operation:

{
  status : "published" | "scheduled" | "failed",
  messageId: messageIdValue
}

Errors:

The following errors may occur during the message publishing API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error:

Error Code

Description

5003

Invalid repeatExpiresAt date in delivery options.

5007

User does not have the permission to publish messages

5030

Invalid publishAt date in the delivery options.

Examples:

Basic push notification

Targeting a group of devices (grouped by OS)

Targeting specific devices

Delayed publishing

Basic push notification
Blocking Publish

// do not forget to call Backendless.initApp in the app initialization logic

var channel = "TestChannel",
    message = "Hello, world!",
    publishOptions = new Backendless.PublishOptions({
       headers: {
           android-ticker-text: "Your just got a push notification",
           android-content-title: "This is a notification title",
           android-content-text: "Push notifications are cool"
       }
   });

var messageStatus = Backendless.Messaging.publishSync( channel, message, publishOptions );
console.log( "message has been published, message status - " + messageStatus.status );

Non-blocking Publish

// do not forget to call Backendless.initApp in the app initialization logic

var channel = "TestChannel",
    message = "Hello, world!",
    publishOptions = new Backendless.PublishOptions({
       headers: {
           android-ticker-text: "Your just got a push notification",
           android-content-title: "This is a notification title",
           android-content-text: "Push notifications are cool"
       }
   });

Backendless.Messaging.publish( channel, message, publishOptions )
 .then( function( messageStatus ) {
    console.log( "message has been published, message status - " + messageStatus.status );
  })
 .catch( function ( error ) {
    console.log( "error - " + error.message );
 });

 

Targetting a group of devices

Blocking Publish

// do not forget to call Backendless.initApp in the app initialization logic

var channel = "TestChannel",
    message = "Hello, world!",
    publishOptions = new Backendless.PublishOptions({
       headers: {
           android-ticker-text: "Your just got a push notification",
           android-content-title: "This is a notification title",
           android-content-text: "Push notifications are cool"
       }
    }),
    deliveryOptions = new Backendless.DeliveryOptions({
          pushBroadcast: "ANDROID|IOS"
    });
var messageStatus = Backendless.Messaging.publishSync( channel, 
                                                       message, 
                                                       publishOptions, 
                                                       deliveryOptions );

Non-blocking Publish

// do not forget to call Backendless.initApp in the app initialization logic

var channel = "TestChannel",
    message = "Hello, world!",
    publishOptions = new Backendless.PublishOptions({
       headers: {
           android-ticker-text: "Your just got a push notification",
           android-content-title: "This is a notification title",
           android-content-text: "Push notifications are cool"
       }
    }),
    deliveryOptions = new Backendless.DeliveryOptions({
          pushBroadcast: "ANDROID|IOS"
    });
    
Backendless.Messaging.publish( channel, message, publishOptions, deliveryOptions )
 .then( function( messageStatus ) {
    console.log( "message has been published, message status - " + messageStatus.status );
  })
 .catch( function ( error ) {
    console.log( "error - " + error.message );
 });

 

Targeting specific devices
Blocking Publish

// do not forget to call Backendless.initApp in the app initialization logic

var channel = "TestChannel",
    message = "Hello, world!",
    publishOptions = new Backendless.PublishOptions({
       headers: {
           android-ticker-text: "Your just got a push notification",
           android-content-title: "This is a notification title",
           android-content-text: "Push notifications are cool"
       }
    }),
    deliveryOptions = new Backendless.DeliveryOptions({
          pushSinglecast: ["dummyDeviceId_001", "dummyDeviceId_002"] // devices IDs
    });
var response = Backendless.Messaging.publishSync( channel, 
                                                  message, 
                                                  publishOptions, 
                                                  deliveryOptions );
// message has been published, message status is available via response.status
// if publish failed - error is described in response.errorMessage

Non-blocking Publish

// do not forget to call Backendless.initApp in the app initialization logic

var channel = "TestChannel",
    message = "Hello, world!",
    publishOptions = new Backendless.PublishOptions({
       headers: {
           android-ticker-text: "Your just got a push notification",
           android-content-title: "This is a notification title",
           android-content-text: "Push notifications are cool"
       }
    }),
    deliveryOptions = new Backendless.DeliveryOptions({
          pushSinglecast: ["dummyDeviceId_001", "dummyDeviceId_002"] // devices IDs
    });
    
Backendless.Messaging.publish( channel, message, publishOptions, deliveryOptions )
 .then( function( messageStatus ) {
  })
 .catch( function( error ) {
  });

Delayed publishing

Blocking Publish

// do not forget to call Backendless.initApp in the app initialization logic

var channel = "TestChannel",
    message = "Hello, world!",
    publishOptions = new Backendless.PublishOptions({
       headers: {
           android-ticker-text: "Your just got a push notification",
           android-content-title: "This is a notification title",
           android-content-text: "Push notifications are cool"
       }
    }),
    deliveryOptions = new Backendless.DeliveryOptions({
        publishAt: (new Date()).getTime() + 60 * 1000 // 1 minute delay
    });
var messageStatus = Backendless.Messaging.publishSync( channel, 
                                                       message, 
                                                       publishOptions, 
                                                       deliveryOptions );
// message has been published, message status is available via messageStatus.status

Non-blocking Publish

// do not forget to call Backendless.initApp in the app initialization logic

var channel = "TestChannel",
    message = "Hello, world!",
    publishOptions = new Backendless.PublishOptions({
       headers: {
           android-ticker-text: "Your just got a push notification",
           android-content-title: "This is a notification title",
           android-content-text: "Push notifications are cool"
       }
    }),,
    deliveryOptions = new Backendless.DeliveryOptions({
        publishAt: (new Date()).getTime() + 60 * 1000 // 1 minute delay
    });
    
Backendless.Messaging.publish( channel, message, publishOptions, deliveryOptions )
 .then( function( messageStatus ) {
  })
 .catch( function( error ) {
  });

 

Get Message Status

Backendless processes push notification delivery asynchronously. Even when the client app uses blocking/synchronous API, the server creates a push notification delivery task and adds to a queue. Server assigns an ID to the task and returns it to the client as messageId. As a result, push notification delivery status is not available right away. To retrieve the status, the client application must make a separate API call documented below:

Non-blocking API:

Backendless.Messaging.getMessageStatus( messageId )
 .then( function( status ) {
  })
 .catch( function( error ) {
  });

Blocking API:

var status = Backendless.Messaging.getMessageStatusSync( messageId );

where:

messageId        -  ID of the message assigned to the message and returned by the Message Publishing API request.

Return Value:

Method returns a JS object with the following structure:

{
  messageId:<messageId>,
  status:<status>,
  errorMessage:<errorMessage>
}

where:

<messageId>        - ID of the message for which the publishing status is retrieved.

<status>                - can be one of the following "published", "scheduled" or "failed".

<errorMessage>        - contains detailed error message when status is "failed".

 

Publish/Subscribe Messaging

Message Publishing

Application can publish messages to Backendless for subsequent distribution to subscribers. A message must be published to a channel (or a group of channels). Backendless supports unlimited number of channels per application. Channels can be used as a filtering mechanism - channel subscribers see messages published only to the channel. Message publishing supports the following scenarios:

 

Publishing with message headers - headers is a collection of name/value pairs. A subscriber can set filters expressed as SQL "where clause" queries which Backendless uses to determine if a message should be delivered to the subscriber. When the query matches the published data in message headers, message is delivered to the corresponding subscriber. See example.
 
Publishing to a subtopic - Subtopics provide an additional level of message filtering.

Multiple subtopics can be defined within a channel. Both publishers and subscribers can specify a subtopic within a channel. Subtopic names can be defined using a multi-tiered format:

maintoken[.secondaryToken][.additionalToken]

 

To receive messages from more than one subtopic, subscribers can use the wildcard character (*) in place of any tokens in the subtopic name. For instance, a subscriber could subscribe to the following subtopic: "news..business.*", and the publisher sends messages to "news.business.newyork" and "news.business.tokyo". In this case the messages published to either subtopic will be delivered to the consumer.

 

The wildcard character in the last position will match any token in that position as well as tokens after it. For instance, subtopic com.foo.* will match all of the following: com.foo.bar, com.foo.abc.def, etc. However, the wildcard character in any position other than the last will match only one token. For example, subtopic com.*.foo will match com.abc.foo and com.123.foo, but will not match com.foo.

See example.
 

Delayed publishing - Backendless immediately processes any published messages and delivers them to subscribers without any delay. However, publishers can specify the time when the message should be processed. This is applicable to all the publishing options listed above. Message processing can be canceled at any time using the message cancellation API. See example.
 
Scheduled (repeated) publishing - Backendless supports repeated message processing - a message is published once, but delivered to subscribers with the specified frequency. Repeated delivery can stop either at the specified time or they can be canceled using the message cancellation API. For instance, this could be used for reminders or scheduled tasks. See example.

 

Method Signatures

Non-blocking method:

Backendless.Messaging.publish( channelName, 
                               message, 
                               publishOptions, 
                               deliveryOptions )
 .then( function( response ) {
  })
 .catch( function( error ) {
  });

Blocking method:

var response = Backendless.Messaging.publishSync( channelName, 
                                                  message, 
                                                  publishOptions, 
                                                  deliveryOptions );

where:

channelName - name of the channel to publish the message to. If the channel does not exist, Backendless automatically creates it.
message - object to publish. Can be a primitive value, an array or a complex type.
publishOptions - optional argument. An instance of the Backendless.PublishOptions class. Contains publisher ID, message headers and subtopic name.
deliveryOptions - optional argument. An instance of the Backendless.DeliveryOptions class. May specify message delivery policy, timestamp (in milliseconds) for publishing at the specified time in the future, interval for repeated publishing.

Return value:

An untyped JavaScript object containing message ID and the status of the publishing operation:

{
  status : "published" | "scheduled" | "failed",
  messageId: messageIdValue
}

Errors:

The following errors may occur during the message publishing API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error:

 

Error Code

Description

5003

Invalid repeatExpiresAt date in delivery options.

5007

User does not have the permission to publish messages

5030

Invalid publishAt date in the delivery options.

Examples:

Basic message publishing

Publishing with message headers

Publishing to a subtopic

Delayed publishing

Repeated publishing

 

Basic message publishing

Non-blocking publish

var channel = "TestChannel",
    message = "Hello, world!";

Backendless.Messaging.publish( channel, message )
 .then( function( response ) {
  })
 .catch( function( error ) {
  });

Blocking publish

var channel = "TestChannel",
    message = "Hello, world!";

var response = Backendless.Messaging.publishSync(channel, message);
// message has been published, message status is available via response.status
// if publish failed - the error is described in response.errorMessage

Publishing with message headers

Non-blocking Publish

var channel = "TestChannel",
    message = "Hello, world!",
    pubOps = new Backendless.PublishOptions({
        headers: {
            city: "Denver",
            state: "Colorado"
        }
    });
    
Backendless.Messaging.publish( channel, message, pubOps )
 .then( function( response ) {
  })
 .catch( function( error ) {
  });

Blocking Publish

var channel = "TestChannel",
    message = "Hello, world!",
    pubOps = new Backendless.PublishOptions({
        headers: {
            city: "Denver",
            state: "Colorado"
        }
    });
var response = Backendless.Messaging.publishSync( channel, message, pubOps );
// message has been published, message status is available via response.status
// if publish failed - error is described in response.errorMessage

Publishing to a subtopic
Non-blocking Publish

var channel = "TestChannel",
    message = "Hello, world!",
    pubOps = new Backendless.PublishOptions({
        subtopic:  "sightseeing"
    });
    
Backendless.Messaging.publish( channel, message, pubOps )
 .then( function( response ) {
  })
 .catch( function( error ) {
  });

Blocking Publish

var channel = "TestChannel",
    message = "Hello, world!",
    pubOps = new Backendless.PublishOptions({
        subtopic:  "sightseeing"
    });
var response = Backendless.Messaging.publishSync( channel, message, pubOps );
// message has been published, message status is available via response.status
// if publish failed - error is described in response.errorMessage

Delayed publishing

Non-blocking Publish

var channel = "TestChannel",
    message = "Hello, world!",
    pubOps = null,
    deliveryOps = new Backendless.DeliveryOptions({
        publishAt: (new Date()).getTime() + 60 * 1000 // 1 minute delay
    });
    
Backendless.Messaging.publish( channel, message, pubOps, deliveryOps )
 .then( function( response ) {
  })
 .catch( function( error ) {
  });

Blocking Publish

var channel = "TestChannel",
    message = "Hello, world!",
    pubOps = null,
    deliveryOps = new Backendless.DeliveryOptions({
        publishAt: (new Date()).getTime() + 60 * 1000 // 1 minute delay
    });
var response = Backendless.Messaging.publishSync( channel, message, pubOps, deliveryOps );
// message has been published, message status is available via response.status
// if publish failed - error is described in response.errorMessage

 

Repeated publishing
Non-blocking Publish

var channel = "TestChannel",
    message = "Hello, world!",
    pubOps = null;
    deliveryOps = new Backendless.DeliveryOptions({
        // frequency in seconds
        repeatEvery: 10, 
        
        // expiration timestamp (in milliseconds), eg: after 1 minute
        repeatExpiresAt: (new Date()).getTime() + 60 * 1000 
    });
    
Backendless.Messaging.publish( channel, message, pubOps, deliveryOps )
 .then( function( response ) {
  })
 .catch( function( error ) {
  });

Blocking Publish

var channel = "TestChannel",
    message = "Hello, world!",
    pubOps = null,
    deliveryOps = new Backendless.DeliveryOptions({
        // frequency in seconds
        repeatEvery: 10, 
        
        // expiration timestamp (in milliseconds), eg: after 1 minute
        repeatExpiresAt: (new Date()).getTime() + 60 * 1000 
    });
var response = Backendless.Messaging.publishSync( channel, 
                                                  message, 
                                                  pubOps, 
                                                  deliveryOps );
// message has been published, message status is available via response.status
// if publish failed - error is described in response.errorMessage

Get Message Status

Backendless server handles message publishing as an autonomous task. As a result, the status of the message publishing operation is not available right away. To retrieve publishing status, the client application must make a separate API call documented below:

Non-blocking API:

Backendless.Messaging.getMessageStatus( messageId )
 .then( function( status ) {
  })
 .catch( function( error ) {
  });

Blocking API:

var status = Backendless.Messaging.getMessageStatusSync( messageId );

where:

messageId        -  ID of the message assigned to the message and returned by the Message Publishing API request.

Return Value:

Method returns a JS object with the following structure:

{
  messageId:<messageId>,
  status:<status>,
  errorMessage:<errorMessage>
}

where:

<messageId>        - ID of the message for which the publishing status is retrieved.

<status>                - can be one of the following "published", "scheduled" or "failed".

<errorMessage>        - contains detailed error message when status is "failed".

Message Filtering

Backendless message filtering is a powerful mechanism enabling conditional message delivery, interest-based subscriptions and private messaging. A subscription request may include filters in the form of subtopics and selectors. Backendless applies subscriber's filters to every message published into the channel and they match, the message is delivered to the subscriber.

 

Subtopics

Multiple subtopics can be defined within a channel. Both publishers and subscribers can specify a subtopic within a channel. Subtopic names can be defined using a multi-tiered format:

maintoken[.secondaryToken][.additionalToken]

 

To receive messages from more than one subtopic, subscribers can use the wildcard character (*) in place of any tokens in the subtopic name. For instance, a subscriber could subscribe to the following subtopic: "news..business.*", and the publisher sends messages to "news.business.newyork" and "news.business.tokyo". In this case the messages published to either subtopic will be delivered to the consumer.

 

The wildcard character in the last position will match any token in that position as well as tokens after it. For instance, subtopic com.foo.* will match all of the following: com.foo.bar, com.foo.abc.def, etc. However, the wildcard character in any position other than the last will match only one token. For example, subtopic com.*.foo will match com.abc.foo and com.123.foo, but will not match com.foo.

 

Selectors

A selector is a query expressed using the SQL-92 syntax and formatted as the condition part of the SQL's WHERE clause. A query condition must reference the headers of the published messages. When a message is published and a subscriber has a selector query, Backendless executes the query on the headers of the published message. If the result of the query is true, the message is delivered to the subscriber. Consider the following example where the subscriber will receive only messages containing the "city" header with the value of "Tokyo":

 

Publisher:

var channel = "TestChannel",
    weather = { temperature:70, humidity:80 },
    headers = { city: "Tokyo" },
    pubOps = new Backendless.PublishOptions( headers );

Backendless.Messaging.publish( channel, message, pubOps )
 .then( function( response ) {
  })
 .catch( function( error ) {
  });

Subscriber:

var channel = "TestChannel",
   callback = function (data) {
        var messagesArray = data["messages"];
    },
    subOps = new Backendless.SubscriptionOptions({
        // string value identifying subscriber 
        subscriberId: "mySubscriberID",     
        
        // string value - name of the subtopic to subscribe to
        subtopic: "general",           
        
        // string query in the SQL-92 format (the where clause)
        selector:  "city = 'Tokyo'"    
    });
Backendless.Messaging.subscribe(channel, callback, subOps)
 .then( function( subscription ) {
        // subscription contains subscriptionId 
        // (obtain it via subscription.subscriptionId)
  })
 .catch( function( error ) {
  });

 

Message Subscription

In order to receive published messages, application must subscribe to a channel using the API described below. Using the API, an application becomes an "API subscriber".

   

Backendless.Messaging.subscribe(channelName, subscriptionCallback, subscriptionOptions)

where:

channelName - ID of the message to cancel.
subscriptionCallback - a callback function where the messaging system delivers published messages for the subscription.
subscriptionOptions - optional argument. An instance of Backendless.SubscriptionOptions which can be used to set subscriber ID, subtopic or selector. See the Message Filtering section for additional details.

Return value:

An object identifying the subscription. Should be used to cancel subscription.

Errors:

The following errors may occur during the message cancellation API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error:

Error Code

Description

5008

User does not have permission to create a subscription.

5009

General subscription error. See error message for additional details.

5010

Unknown messaging channel.

Message Object

The JavaScript clients retrieve messages from Backendless using short polling. The subscriptionCallback function in the subscribe call receives published messages. The function's argument is an untyped object with the "messages" property. The property contains an array of messages published to the channel since the previous polling request.

var subscriptionCallback = function (data) {
  var messagesArray = data["messages"];
  // process messages here
}

Each message object in the array has the following structure:

{
  messageId: <messageId>,     // ID of the message
  headers: <headersObj>,      //JS object containing message headers
  data: <data>,               // actual message object published by the publisher
  publisherId: <publisherId>, // publisher ID
  timestamp: <timestamp>      // timestamp when the message was published
}

where:

messageId - unique message ID. The ID is assigned at the time of message publishing.
headers - an associative array which is a collection of key/value pairs. Includes all the headers included with the message publishing. Additionally, Backendless adds the following headers: BL_APPLICATION_ID - contains the ID of the application and BL_VERSION_URL_PREFIX - contains the name of the version of the application.
data - message payload. It is the object sent by a publisher.
publisherID - the property contains sender (publisher) ID if it is provided by the publisher.
timestamp - a timestamp indicating when the message was received by Backendless from the publisher.

 

Examples:

Non-blocking Subscribe:

var channel = "TestChannel",
    subOps = new Backendless.SubscriptionOptions({ 
        // string value identifying subscriber
        subscriberId: "myDummyID",     
        
        // string value - name of the subtopic to subscribe to
        subtopic: "general",           
        
        // string query in the SQL-92 format (the where clause)
        "selector-query": "<value>"    
    }),
    callback = function (data) {
        var messagesArray = data["messages"];
    };

Backendless.Messaging.subscribe( channel, callback, subOps )
 .then( function( subscription ) {
  })
 .catch( function( error ) {
  });

Blocking Subscribe:

var channel = "TestChannel",
   callback = function (data) {
        var messagesArray = data["messages"];
    },
    subOps = new Backendless.SubscriptionOptions({ 
        
        // string value identifying subscriber
        subscriberId: "myDummyID",     
        
        // string value - name of the subtopic to subscribe to
        subtopic: "general",           
        
        // string query in the SQL-92 format (the where clause)
        "selector-query": "<value>"    
    });

var subscription = Backendless.Messaging.subscribeSync(channel, callback, subOps);
// subscription contains subscriptionId (obtain it via subscription.subscriptionId)

Cancel Subscription

In order to stop a client from polling for messages, it must issue subscription cancellation request using the API method described below:

 

Method Signatures:

subscriptionObject.cancelSubscription()

where:

subscriptionObject        - object received as a result of the subscribe operation.

 

Example:

Non-blocking Subscribe and Cancel:

var channel = "TestChannel",
    subOps = new Backendless.SubscriptionOptions({ 
        // string value identifying subscriber
        subscriberId: "myDummyID",     
        
        // string value - name of the subtopic to subscribe to
        subtopic: "general",      
        
        // string query in the SQL-92 format (the where clause)     
        selector: "<value>"    
    }),
    callback = function (data) {
        var messagesArray = data["messages"];
    };

Backendless.Messaging.subscribe( channel, callback, subOps )
 .then( function( subscription ) {
    setTimeout(function () {
        subscription.cancelSubscription(); // cancelling subscription after 7 seconds timeout
    }, 7000);
  })
 .catch( function( error ) {
  });

Blocking Subscribe and Cancel:

var callback = function (data) {
    var messagesArray = data["messages"];
};

var subscription = Backendless.Messaging.subscribeSync('TestChannel', callback);

setTimeout(function () {
    // cancelling subscription after a 7 seconds timeout
    subscription.cancelSubscription(); 
}, 7000);

Cancel Scheduled Message

Delayed or scheduled messages can be canceled using the API documented below. Backendless processes delayed messages at the time specified by the publisher. Scheduled messages are processed and delivered with a specified interval.

 

Method Signatures

Non-blocking Method:

Backendless.Messaging.cancel( messageId )
 .then( function( messageStatus ) {
  })
 .catch( function( error ) {
  });

Blocking Method:

var messageStatus = Backendless.Messaging.cancelSync( messageId );

where:

messageId - ID of the message to cancel. Message ID must be obtained from the MessageStatus object obtained as the result of the Publish methods.

 

Return value:

Method returns messageStatus object with the following structure:

{
  status : value,
  messageId: messageIdValue
}
If the status property is "cancelled", the cancellation has been successful.

Errors:

The following errors may occur during the message cancellation API call. See the Error Handling section for details on how to retrieve the error code when the server returns an error:

Error Code

Description

5040

Message has already been canceled or does not exist.

Example:

Non-blocking Publish then Cancel:

var channel = "TestChannel",
    message = "Hello, world!",
    pubOps = null;
    deliveryOps = new Backendless.DeliveryOptions({
          publishAt: (new Date()).getTime() + 60 * 1000 // 1 minute delay
    });
    
Backendless.Messaging.publish( channel, message, pubOps, deliveryOps )
 .then( function( messageStatus ) {
   Backendless.Messaging.cancel( response.messageId );
 })
.catch( function( error ) {
 });

Blocking Publish then Cancel:

var channel = "TestChannel",
    message = "Hello, world!",
    pubOps = null;
    deliveryOps = new Backendless.DeliveryOptions({
         publishAt: (new Date()).getTime() + 60 * 1000 // 1 minute delay
    });
var response = Backendless.Messaging.publishSync( channel, 
                                                  message, 
                                                  pubOps, 
                                                  deliveryOps );
Backendless.Messaging.cancelSync(response.messageId);

 

Files API

Overview

Every Backendless backend/app is allocated a dedicated file storage space. The file storage is located remotely on the Backendless servers. The file storage can be used to store application's files and on-demand video streams.  Backendless File Service provides the API to work with the file storage. The API supports the following operations:

 

File Upload - upload files to the applications's file storage. The operation creates directories up the hierarchy if necessary. Returns file URL which can be used to download or share the file with others.
File Download - download file using file's URL. The download operation is subject to the permissions from the File access control list (ACL).
File Deletion - delete a file from the file storage. The delete operation is subject to the permissions from the File access control list (ACL).
Directory Deletion - same as file deletion, but applies to the directories.
File/Directory Security (File ACL) - assign/unassign user and roles permissions to upload, download and delete files and directories. This API is used to modify file or directory ACL.

 

In addition to the API implementation, the File Service enables the following capabilities:

 

Git Integration - application developers can interact with the file storage as with a git repository.
Web Hosting - file storage can be used to host static web content.
Custom Domain Name - a custom domain name can be mapped to the file storage in a Backendless backend. This feature in combination with the Web Hosting provides a way to host websites on Backendless.
Custom Web Templates Hosting - includes HTML files and JS scripts for special pages used in various workflows such as user email confirmation, password change and session expiration.

Handling Files in Console

Backendless Console includes a graphical file browser which supports the following operations:

Creating a New File

To create a file:

1. Log in to Backendless Console and select an application. Click the Files icon:
files-screen1.zoom50
2. Select a directory where a new file should created (or create a new directory). Click the New File link from the toolbar.
3. Enter a name in the File name field. Additionally, you can select syntax highlighter from the menu located in the lower-right corner. The highlighter selection is available only for new files. When an existing file is opened for editing, a highlighter is automatically selected based on the file extension.
files-screen2.zoom50
4. To save the file click the Save or the Save and Close button.

Editing a File

To edit a file:

1. Select a directory containing the file on the Files screen of the console.
2. Click the Edit file icon in the Actions column for the file to open it for editing:

files-screen3

3. Once the changes in the file are made click the Save or the Save and Close button.

Getting Public URL for a File

A file in the Backendless File Storage has two URLs:

a public URL which can be used to download the file outside of Backendless console. This URL is subject for any permissions assigned to the file or the directory where it resides.
a private URL which makes the file accessible to the developer of the application.

In order to obtain the public URL:

1. Select a directory containing a file on the Files screen of Backendless console.
2. Click the Get Public URL icon next to the file. Backendless console copies the file's public URL to the computer's clipboard:

files-screen4

To get file's private URL:

Right click file name and select "Copy Link Address" (text of the menu may vary depending on the browser):
files-screen5

File's private URL works only if the request to download the file contains the auth-key HTTP header. The value of the header is a token assigned by Backendless to the developer login to console.

Archiving Files

Backendless Console includes a feature enabling to compress directories into a single ZIP file. The feature applies specifically to directories, meaning an individual file cannot be compressed - it must be placed into a directory first.

Notice: archiving of directories with total content size greater than 100 Mb may take longer time; Backendless sends an email to the application developer upon successful completion of the operation.

To archive a directory:

1. Log in to Backendless Console, select an application and click the Files icon.
2. Navigate to a directory which should be compressed.
3. Click the ZIP Directory button:
files-screen6
4. Once the directory is compressed into an archive, it will appear in the parent directory:
files-screen7

File Upload

The file upload operation delivers and saves a local file in the remote Backendless file storage. The return value of the operation is the file URL which has the following structure:

https://api.backendless.com/<application id>/<REST-api-key>/files/<path>/<file name>

where:

<application id> - ID of the application which can be obtained from the Manage > App Settings screen of the Backendless Console
<REST-api-key> - REST API key of your application. You can get the value from the Manage > App Settings section of the Backendless Console.
<path> - directory path where the file is saved
<file name> - name of the file

The URL assigned to a file and returned as a result of the upload operation accounts for any security permissions assigned to the file (or the folder it is located in).

Non-blocking method:

// single file upload
Backendless.Files.upload( file, path, overwrite )
 .then( function( fileURL ) {
  })
 .catch( function( error ) {
  });

Blocking method:

// single file upload
var fileURL = Backendless.Files.uploadSync( file, path, overwrite );

where:

file - an instance of JavaScript File class.
overwrite - a boolean value indicating whether the upload must overwrite an existing file.
path - directory path (without the name of the file) in the Backendless file storage where the file should be stored. If the path does not exist, Backendless File Service creates the directory structure.

Return Value:

URL of the uploaded file.

Example:

HTML:

<input type="file" id="files" name="files[]" multiple />
<input type="button" onclick="uploadFileFunc(); return false;" value="Upload File"/>
Javascript:
// this line goes into the app initialization block
document.getElementById('files').addEventListener('change', handleFileSelect, false);
 
function handleFileSelect(evt) 
{
   file = evt.target.files[0]; // FileList object
}
 
function uploadFileFunc()
{
  var callback = {};
 
   callback.success = function(result)
   {
       
   }
 
   callback.fault = function(result)
   {
       alert( "error - " + result.message );
   }
 
   Backendless.Files.upload( file, "my-folder" )
    .then( function( fileURLs ) {
       console.log( "File successfully uploaded. Path to download: " + result.fileURL );
     })
    .catch( function( error ) {
       console.log( "error - " + error.message );
     });

Save Files From Byte Arrays

In addition to the classic file upload, files can be saved by uploading a byte array which becomes the content of the saved file.

Non-blocking method:

Backendless.Files.saveFile( path, fileName, fileContent, overwrite )
 .then( function( fileURL ) {
  })
 .catch( function( error ) {
  });

Blocking method:

var fileURL = Backendless.Files.saveFileSync( path, fileName, fileContent, overwrite );

where:

path - path of the directory where the file should be stored. Must start with "/" which represents the root directory of the remote file storage.
fileName - name of the file where the byte content should be written to.
fileContent - an array of bytes to save.
overwrite - the file is overwritten if the argument value is true and the file already exists. Otherwise, if the value is false and another file with the same name already exists, an error  is returned.

 

Example:

The example below describes how to save a file called "fox.txt" from the string "The quick brown fox jumps over the lazy dog." You will need to specify:

where to save the new file ("testfolder")
a name of the newly created file ("fox.txt")
the byte array that is to become a new file's content (var byteArray = new Blob([<fileContent>]))
whether a new file should overwrite the existing file, if any (true)

var byteArray = new Blob( [<fileContent>] ); 

Backendless.Files.saveFile( "testfolder", "fox.txt", byteArray, true )
 .then( function( savedFileURL ) {
    console.log( "file has been saved - " + savedFileURL );
  })
 .catch( function( error ) {
    console.log( "error - " + error.message );
  }); 

The server will return the link to the newly added file or an error.

Errors:

Error codes returned on attempt to save a file from the byte array.

Error Code

Description

6016

When saving a new file from the byte array, the payload exceeds 2,800,000 bytes.

6003

A file you are trying to save already exists in the system and cannot overwrite since overwrite argument is ether set to false or omitted.

 

File Download

Downloading a File via the Backendless Console

To download a file:

1. Log in to Backendless Console and select the application containing the file.
2. Click the Files tab on the left menu.
3. Locate a file you want to download. Click the Download file icon.

files-screen8

Downloading a File via API

Downloading a file from the Backendless file storage is the basic HTTP GET operation. The operation should use the same URL which Backendless returned as the result of the file upload operation. Alternatively, if the file was uploaded manually using the console, the URL can be composed as:

https://api.backendless.com/<application id>/<REST API key>/files/<path>/<file name>

where:

<application id> - ID of the application which can be obtained from the Manage > App Settings screen of the Backendless Console.
<REST API key> - REST API key assigned to the application by Backendless. The key is available from the Manage > App Settings screen of the Backendless Console.
<path> - Directory path where the file is saved.
<file name> - Name of the file.

Files fetched with the URL scheme defined above are subject to the security constraints and permissions established by the application developer. See the Files Security section for additional details on how to secure file storage. Fetching a file secured by an access control list (ACL) policy requires an additional HTTP header in the request:

user-token:<value>

where:

<value> - a value identifying currently logged in user (if any). This is the value received in the response for the login API request.  The token uniquely identifies the user and the roles associated with him. It is used by Backendless to establish user's identity for all operations where the token is present. It is necessary in order to determine permissions applicable to the user and the roles associated with the account. This header is optional.

Renaming a File/Directory

 
Non-blocking method:

Backendless.Files.renameFile(oldPathName, newName)
 .then( function( newPath ) {
  })
 .catch( function( error ) {
  });

Blocking method:

var newPath = Backendless.Files.renameFileSync(oldPathName, newName);

where:

oldPathName - a path identifying file or directory to be renamed. The must start with the root directory for the file storage allocated to the application.
newName - new name for the file or directory.

Return value:

Absolute path of the renamed file or directory.

Example:

The example below describes how to rename a file called readme.txt located in the /documentation directory to readme-first.txt:

Backendless.Files.renameFile( "/documentation/readme.txt", "readme-first.txt" )
 .then( function( newPath ) {
    console.log( "file has beed renamed - " + newPath );
  })
 .catch( function( error ) {
    console.log( "error " + error.message );
  });

Errors:

The server may return the following errors:

Error Code

Error message

Notes

4000

User has no permissions to specified resource

Occurs when the user (anonymous or logged in) has no permission to modify the file

6000

File or directory cannot be found.

Occurs when the source file/directory cannot be found.

6006

Path contains prohibited symbols: {path name}


6007

The specified resource was not found

Occurs when the API requests to rename a non-existent file or a directory.

6018

Unable to modify file/folder: file/folder already exists: {file/directory name}

Occurs when the target file/directory already exists.

6028

Parameter {param name} cannot be null

Occurs when one of the parameters is null.

8010

Missing field {field name}

Occurs in REST APIs when one of the required fields is missing in the body.

Copying a File/Directory

Non-blocking Method:

Backendless.Files.copyFile(sourcePath, targetPath)
 .then( function( filePath ) {
  })
 .catch( function( error ) {
  });

Blocking method:

var filePath = Backendless.Files.copyFileSync(sourcePath, targetPath);

where:

sourcePath - a path identifying file or directory to be copied. The path must start with the root directory of the remote file storage.
targetPath - a path to a directory where the source file or directory should be copied to. If the directory does not exist, it is created.

Return value:

Absolute path to the copied file or directory.

Example:

The example below describes how to copy a file called readme.txt located in the /documentation directory to the /updated-docs directory:

Backendless.Files.copyFile( "/documentation/readme.txt", "/updated-docs" )
 .then( function( filePath ) {
    console.log( "file has been copied. File path - " + filePath );
  })
 .catch( function( error ) {
    console.log(  "error - " + error.message );
  });

Errors:

The server may return the following errors:

Error Code

Error message

Notes

4000

User has no permissions to specified resource

Occurs when the user (anonymous or logged in) has no permission to modify the file

6000

File or directory cannot be found.

Occurs when the source file/directory cannot be found.

6006

Path contains prohibited symbols: {path name}


6007

The specified resource was not found

Occurs when the API requests to rename a non-existent file or a directory.

6018

Unable to modify file/folder: file/folder already exists: {file/directory name}

Occurs when the target file/directory already exists.

6028

Parameter {param name} cannot be null

Occurs when one of the parameters is null.

8010

Missing field {field name}

Occurs in REST APIs when one of the required fields is missing in the body.

8011

Content type should be 'application/json'

Occurs in REST API when the request's content type is not application/json

Moving a File/Directory

Non-blocking method:

Backendless.Files.moveFile(sourcePath, targetPath)
 .then( function( filePath ) {
  })
 .catch( function( error ) {
  });

Blocking method:

var filePath = Backendless.Files.moveFileSync(sourcePath, targetPath);

where:

sourcePath - a path identifying file or directory to move. The path must start with the root directory of the remote file storage.
targetPath - a path to a directory where the source file or directory should be moved to. If the directory does not exist, it is created.

Return value:

Absolute path to the copied file or directory.

Example:

The example below describes how to move a file called readme.txt located in the /documentation directory to the /updated-docs directory:

Backendless.Files.moveFile( "/documentation/readme.txt", "/updated-docs" )
 .then( function( filePath ) {
    console.log( "file has been moved - " + filePath );
  })
 .catch( function( error ) {
    console.log( "error - " + error.message );
  });

Errors:

The server may return the following errors:

Error Code

Error message

Notes

4000

User has no permissions to specified resource

Occurs when the user (anonymous or logged in) has no permission to modify the file

6000

File or directory cannot be found.

Occurs when the source file/directory cannot be found.

6006

Path contains prohibited symbols: {path name}


6007

The specified resource was not found

Occurs when the API requests to rename a non-existent file or a directory.

6018

Unable to modify file/folder: file/folder already exists: {file/directory name}

Occurs when the target file/directory already exists.

6021

Unable to move file to file


6023

File already exists in target directory


6028

Parameter {param name} cannot be null

Occurs when one of the parameters is null.

8010

Missing field {field name}

Occurs in REST APIs when one of the required fields is missing in the body.

8011

Content type should be 'application/json'

Occurs in REST API when the request's content type is not application/json

Directory Listing

Non-blocking method:

Backendless.Files.listing(path, pattern, recursively, pagesize, offset)
 .then( function( fileInfoArray ) {
  })
 .catch( function( error ) {
  });

Blocking method:

var fileInfoArray = Backendless.Files.listingSync(path, pattern, recursively, pagesize, offset);

where:

path - path of a directory to get a listing of. The path must start with the root directory identified by forward slash, for example: "/web".
pattern - an optional parameter. A pattern which the returned files and directories must match. The pattern can include wildcard characters. Asterisk (*) matches a substring of any length. Question mark (?) matches only one character in the file/directory name.
recursive - an optional parameter. A boolean value of true or false. If present and is true, requests that the listing must be retrieved recursively for all directories contained within <path>.
pagesize - an optional parameter. If present, identifies how many items should be returned in the response.
offset - an optional parameter. If present, indicates the index of item in the response from which to get the <pagesize> items.

Return value:

A collection of files and directories matching the specified pattern. The returned collection may not include all files and directories as the size of the number of returned entries is limited for paging purposes. To get the next set of file/directory entries, an additional request must be made with a different offset value. The total count of all files/directories in the listing use the Get File Count API.

Each element in the collection contains the following properties:

name - name of the file or directory without any path information
public URL - absolute URL of the file or directory
URL - relative URL of the file or directory starting from the root of the file storage
created on - a timestamp indicating when the file or directory were created

Example:

The example below describes how to get a listing of all HTML files from the /web directory and all subdirectories.

Backendless.Files.listing( "/web", "*.html", true )
 .then( function( fileInfoArray ) {
  })
 .catch( function( error ) {
  });

Errors:

The server may return the following errors:

Error Code

Error message

Notes

6000

File or directory cannot be found.

Occurs when the source file/directory cannot be found.

6006

Path contains prohibited symbols: {path name}


6007

The specified resource was not found

Occurs when the API requests to rename a non-existent file or a directory.

6029

Specified resource must be a directory

Occurs when the path to get a listing for is not a directory

Get File Count

This operation returns the number of files and optionally directories located in the specified path. Additional options include:

 

pattern filtering - counts files and directories which match the pattern.
recursive counting - when enabled, counts all matching files and directories while recursively traversing the directory structure.

 

Deleting a File

To delete a file from the Backendless file storage, it must be identified by the file path/name. Files in the Backendless storage have the following URL structure:

https://api.backendless.com/<application id>/<REST API key>/files/<path>/<file name>

The API to delete a file uses the <path>/<filename> part to identify the file which must be deleted.

Non-blocking method:

Backendless.Files.remove( filePath )
 .then( function() {
 })
 .catch( function( error ) {
 });

Blocking method:

Backendless.Files.removeSync( filePath );

where:

filePath - Path of the file to delete. The path must consist of the file path and file name.

Return Value:

None.

Example:

Backendless.Files.remove( "my-folder/myfile.txt" )
 .then( function() {
   console.log( "file has been deleted" );
 })
 .catch( function( error ) {
   console.log( "error - " + error.message );
 });

 

Deleting a Directory

To delete a directory from the Backendless file storage, it must be identified by the its path. Directories in the Backendless storage have the following URL structure:

https://api.backendless.com/<application id>/<REST API Key>/files/<path>

The API to delete a directory uses the <path> element from the URL above.

Non-blocking method:

Backendless.Files.removeDirectory( path )
 .then( function() {
 })
 .catch( function( error ) {
 });

Blocking method:

Backendless.Files.removeDirectorySync( path );

where:

path - path of the directory to delete.

Return Value:

None.

Example:

Backendless.Files.remove( "my-folder/pics" )
 .then( function() {
   console.log( "directory has been deleted" );
 })
 .catch( function( error ) {
   console.log( "error - " + error.message );
 });

 

Git Integration

Backendless file storage can also function as a git repository. This could be very convenient for deploying multiple files from the developer's computer with a single command. Git integration is disabled by default. To enable git for the file storage:

1. Open Backendless Console
2. Select your app/backend
3. Click Manage and scroll down to the Enable .git support section
4. Use the toggle to turn git integration on or off:
git-support

When the git integration is turned on, all files present in or uploaded to the file storage are immediately committed to the repository. This integration is bi-directional. It means that any files committed into the git repository by the means of git, will also be copied into the file storage. When git integration is being turned off, the git repository is deleted with all the associated history (the files remain in the file storage).

With the git integration enabled, a new folder (.git) appears in the File Browser on the Files screen. The folder contains the files from the git repository. When a file is uploaded to file storage either via the Upload API or using the File Browser, it is automatically committed to the repository. Likewise, when a file is pushed into the repository, it becomes available and visible in the file storage. The same applies to editing and deleting files either in the Backendless Console or in git repository.

When git is enabled, the repository is available at the following address:

https://gitv4.backendless.com/<application id>/.git

where:

<application id> - application ID available in Backendless Console at Manage > App Settings.

When the Backendless backend is configured with a custom domain name, the repository URL is:

http://<custom domain name>/.git

The repository uses the same authentication as Backendless Console. That means all git commands must use the same developer email address and password as for logging in to Backendless Console.

It is important to note that any system level files created by git are also placed into the file storage (the .git directory). These files are accounted for when calculating the file space used by the app/backend.

Configuring Local Environment

You have no local files and there is a remote GIT repository:

There are files in the Backendless storage and there are no files locally:

Clone existing repository:

mkdir /path/to/your/project
cd /path/to/your/project
git clone http://gitv4.backendless.com/<your application id>/.git
cd <your application id>

Adding a file locally and pushing to Backendless git:

> echo "First file" >> file.txt
> git add file.txt
> git commit -m 'Initial commit with new file'
> git push -u origin master

You have with an existing GIT project in your local environment:

This applies when you already have a local git project. You also enabled git integration in Backendless and need to "integrate" your local git project with the git repository in Backendless.

> cd /path/to/my/repo
> git remote add origin http://gitv4.backendless.com/<your application id>/.git
 
# pushes the repo and its refs for the first time to Backendless git
> git push -u origin --all
 
# pushes any tags to Backendless git
> git push -u origin --tags

You have an existing FILE project in your local environment.

This applies when you have existing files locally and need to add them to the git repository you initialized in Backendless.

> cd /path/to/my/repo
> git init
> git remote add origin http://gitv4.backendless.com/<your application id>/.git
> git pull -u origin master
> git add *
> git commit -m 'merge with existing project'
> git push -u origin master

Web Hosting

Backendless file storage includes a special directory which facilitates web hosting for the app/backend. The directory name is /web:

web-directory

The /web folder serves as the web server root. The web server is available at the following URLs:

With custom domain name enabled for the account:

http://custom domain name

Without custom domain name:

https://api.backendless.com/<application id>/<REST API key>/files/web

where:

<application id> - ID of the application which can be obtained from the Manage > App Settings screen of the Backendless Console
<REST API Key> - API Key assigned to the application by Backendless. It is available from the Manage > App Settings screen of the Backendless Console.

 

Custom Domain Name

Backendless File Service supports mapping of a custom domain name to the application's backend. As a result, once a domain name is mapped, the following backend's resources become available via the custom URL:

Service API endpoint. The default endpoint for all Backendless services is:

https://api.backendless.com

With a custom domain name, the endpoint is also available at:

http://<custom domain name>/api

Web Hosting. Backendless file storage contains a special directory - /web, which serves as the web site root. When a custom domain name is mapped to a Backendless application/backend, the contents of the /web directory are served for the HTTP requests with the domain name. See the Web Hosting section for additional details.
git endpoint. When the Backendless git integration is enabled, the git endpoint with a custom domain name is:

http://<custom domain name>/.git

Before a custom domain name is assigned to a Backendless application:

1. Create a CNAME record in DNS for your custom domain name
2. Map the CNAME record to develop.backendless.com
3. Open Backendless Console and select your application/backend.
4. Click Manage and scroll down to the "Custom Domain" section.
5. Enter the domain name into the text field and click Save
custom-domain.zoom50

Custom Web Template Hosting

A client-to-backend workflow may include interaction with web pages presented to the users of the application. Consider the following scenarios:

User registration. When a user registers with an application, he receives an email with a link to a page. Clicking the link acknowledges that the email address is valid and the user account is confirmed.
Password change. When a user requests password change (or password recovery), an email is sent to the user with a link to a web page where they can reset the password.
Session expiration. When a user session with the application expires, he is redirected to a webpage.

All these use cases have something in common - they all force the user to interact with a web page. The templates for these pages are available in the /web/templates path of the backend's file storage:

files-web-templates-dirs

The look and feel as well as the logic in the pages can be customized by modifying the HTML/CSS /JS files provided for each template. For example, the contents of the change_password folder is:

change-password-template

The "out-of-the-box" rendering of the pages is shown below:

Registration confirmation page:

backendless-registration-conf

Password change page:

backendless-password-change

Session expiration page:

session-expiration

Files Security

Access to files and directories can be restricted using permissions. Backendless supports the following permissions for files and directories:

Read - permission to download a file. This permission can be applied to a directory, in that case it applies recursively to all files contained therein.
Write - permission to upload a file or modify a directory by uploading files into it.
Remove - permission to delete a file or a directory.

To modify the permission matrix for a file or a directory, click the "Permissions" icon in file browser in Backendless Console:

files-directory-permissions

 

The permission assignment popup contains two sections: USER PERMISSIONS and ROLES PERMISSIONS which allow permission management either for a specific user account or for application roles.  To modify a permission for an operation for a user or a role, click the icon in the corresponding column. The icon has 3 states:

ico-checkmark-gray - inherit GRANT permission from the global permission matrix. This is the default permission.

ico-checkmark-green - explicit GRANT of the permission for the operation. Allows the user to perform the operation.

icon-deny - DENY permission for the operation. Restricts the user from performing the operation.

files-role-permissions.zoom70

Permissions API

Backendless security mechanism assigns an Access Control List (ACL) to every file in the file storage. An ACL defines users and user roles that have permissions to read, write, or delete a file. The Permissions API allows programmatic control over file ACL by granting or denying permissions to a file for a user or a user role. Using the API permissions can be applies for a user or user role individually or in "bulk" - for all users or user roles in a single call.

The path or the url argument in the APIs below must identify a file or directory for which the permission is modified. The value must be the short version of the path. For example, suppose the full URL of a file in the file storage system is:

https://api.backendless.com/31CB9FED-F34C-5541-FF26-6C2B6719F200/23432-A6B2-FF6B-31CB9FED/files/movies/vacation.mp4

The path to the file in the API call must contain only the directory and the file name (without the leading slash):

movies/vacation.mp4

 

The user account (or the role) on which behalf the API is executed must contain the Permission permission. For example, if the call is made by an authenticated user, the role for the user account would be AuthenticatedUser. The role must have the Permission permission in order for the API call to go through.

 

To grant access for a user

Backendless.Files.Permissions.READ.grantUser( userid, url );
Backendless.Files.Permissions.DELETE.grantUser( userid, url );
Backendless.Files.Permissions.WRITE.grantUser( userid, url );

where:

userid - ID of a user, for which you want to grant the read, delete, or write permission.
url - path to a file, for which you want to specify the permission.

To grant access for a user role

Backendless.Files.Permissions.READ.grantRole( rolename, url );
Backendless.Files.Permissions.DELETE.grantRole( rolename, url );
Backendless.Files.Permissions.WRITE.grantRole( rolename, url );

where:

rolename - name of a user role, for which you want to grant the read, delete, or write permission.
url - path to a file, for which you want to specify the permission.

To grant access for all users

Backendless.Files.Permissions.READ.grant( url );
Backendless.Files.Permissions.DELETE.grant( url );
Backendless.Files.Permissions.WRITE.grant( url );

where:

url - path to a file, for which you want to specify the permission.

To deny access for a user

Backendless.Files.Permissions.READ.denyUser( userid, url );
Backendless.Files.Permissions.DELETE.denyUser( userid, url );
Backendless.Files.Permissions.UPDATE.denyUser( userid, url );

where:

userid - ID of a user for which you want to deny the read, delete, or write permission.
url - path to a file, for which you want to specify the permission.

To deny access for a user role

Backendless.Files.Permissions.READ.denyRole( rolename, url );
Backendless.Files.Permissions.DELETE.denyRole( rolename, url );
Backendless.Files.Permissions.WRITE.denyRole( rolename, url );

where:

rolename - name of a user role, for which you want to deny the read, delete, or write permission.
url - path to a file, for which you want to specify the permission.

To deny access for all users

Backendless.Files.Permissions.READ.deny( url );
Backendless.Files.Permissions.DELETE.deny( url );
Backendless.Files.Permissions.WRITE.deny( url );

where:

url - path to a file, for which you want to specify the permission.

 

Geolocation API

Overview

Backendless Geolocation Service is a system supporting management and search of geo points. A geo point in the most primitive format consists of a pair of coordinates: latitude and longitude. Optionally a geo point may contain metadata, which is a collection of arbitrary key/value pairs. A geo point belongs to a category, which is a logical grouping of geo points. The diagram bellow illustrates these concepts:

backendless-geo-point-concepts.jpeg

 

Backendless allows infinite number of geo points managed for an application. Geo points can be added via an API call or the import functionality in Backendless console. Once the backend is populated with geo points, the search API can be used to run the following types of geo queries:

Radius-based search - Searches for geo points in a circular map area defined by the coordinates of the central point and a radius. Backendless returns all geo points within the area.
backendless-geo-radius-search
Search in a rectangular map area - Searches for geo points in a rectangular map area identified by the coordinates of two corners defining the area (North West and South East):
backendless-geo-rectangle-search

 

Additionally, the geo search API supports the following search options available in the APIs:

Filtering by categories - Both types of search (radius-based and rectangular) can specify the categories in which the backend should search for the geo points.
Query-based search - The metadata associated with the geo points can be used in queries which should be formatted using the SQL-92 syntax. For example, the geo point shown in the image above can be discovered with the following queries:

cuisine = 'French'

cuisine LIKE 'Fr%' and Atmosphere = 'Casual'

cuisine = 'French' and (Price = '$$$$' or Price = '$$$')

Relative search - Runs a search for a subset of metadata key/value pairs to match up to the specified threshold value. The threshold must be expressed as a percentage of matches.

 

Adding a GeoPoint

This API adds a geo point to the backend geo location storage. Once a geo point is added, it becomes searchable through all geopoint search mechanisms supported by Backendless (search in radius, search in a rectangular area, search in a category). At the present moment there are two ways to add geo points: (1) using the API documented below or (2) using the Backendless console's import function.

 

Non-blocking method:

Backendless.Geo.addPoint( geoPoint )
 .then( function( savedGeoPoint ) {
 })
 .catch( function( error ) {
 });

Blocking method:

var savedGeoPoint = Backendless.Geo.addPointSync( geoPoint );

where:

geoPoint - a JavaScript object describing the geo point to add. Must have the following structure:

{
  latitude: <latitude>,
  longitude: <longitude>,
  categories: <categoriesArray>,
  metadata: <metadataObj>
}

where:

<latitude> - latitude of the point to add. Must be a numeric value.
<longitude> - longitude of the point to add. Must be a numeric value.
<categoriesArray> - optional parameter. Array of categories the point is added to. If a category does not exist at the time when a point is added, Backendless creates the category and adds the point to it. If the property is not present in the object, the point is added to the "Default" category. Each category name must be a string.
<metadataObj> - optional parameter. Metadata associated with the geo point. Must be a JavaScript object. Property names of the object become the key names in the metadata properties become values of the corresponding keys. Accepted values for this parameter are: String, Number (integer and double), and Data Service objects. Date values must be represented as number in the Unix timestamp format (number of milliseconds since January 1, 1970 at UTC). Learn more about using date in search queries for category, radius, or rectangular area search.

Return Value:

A JavaScript object representing the new geo point. The object has the following structure:

{
  objectId: <objectId>, // ID assigned to the geo point by Backendless
  latitude: <latitude>,
  longitude: <longitude>,
  categories: <categoriesArray>,
  metadata: <metadataObj>
}

Example:

var point = {
  latitude: 20,
  longitude: 30,
  categories: ["restaurants", "cool_places"],
  metadata: {"owner":"XXXX-XXXX-XXXX-XXXX"}
  }

Backendless.Geo.addPoint( point )
 .then( function( savedGeoPoint ) {
   console.log( "geo point saved " + savedGeoPoint.geopoint.objectId );
 })
 .catch( function( error ) {
   console.log( "error - " + error.message );
 });

 

Updating a GeoPoint

Geo update API relies on the same methods used for Adding a Geo Point. The primary difference is in order to update a geo point it must have the objectId property assigned by Backendless (when the geopoint is created or imported). The semantics of the properties in an update request is as follows:

objectId is a required property.
All other properties (latitude, longitude, categories, metadata) are optional, but at least one must contain a value.
If latitude or longitude contain values, the new values replace the existing ones.
If categories contains a value, the geo point is moved to the specified categories (with coordinates and metadata).
If categories is null, the geo point stays in the current category.
If metadata is null, the geo point keeps the current metadata.
If metadata contains any key/value pairs, the new metadata replaces the existing one.
If metadata is an empty object/dictionary, the existing metadata is removed.

Deleting a GeoPoint

There are two ways to delete a geopoint from the Geolocation storage:

 

Deleting a GeoPoint using the Backendless Console

To delete a geo point using the Backendless Console:

1. Log in to the Backendless Console, select your app and click the Geolocation icon.
2. Select a geo category containing the geopoint to be deleted.
3. Click the checkboxes next to the geopoint(s) which should be deleted.
4. Click Delete Selected from the button bar as shown below:
geo-delete-geopoints.zoom50
5. Click Delete in the confirmation popup to confirm the deletion.
6. A confirmation notification will appear in the top right corner.

 

Deleting a GeoPoint with the API

Non-blocking method:

Backendless.Geo.deletePoint( geoPoint )
 .then( function() {
 })
 .catch( function( error ) {
 });

Blocking method:

Backendless.Geo.deletePointSync( geoPoint );

geoPoint - a JavaScript object describing the geopoint to delete. It may be a geopoint retrieved using the search, save  or update methods.

Return value:

undefined or error

Example:

The example below demonstrates how to delete a geopoint. A geopoint is added first, then subsequently deleted.

function errorHandler( error ) {
  console.log( "error - " + error.message );
};

var geopoint = {
  latitude: 20,
  longitude: 30,
  categories: ["restaurants"]
  };

Backendless.Geo.addPoint( geopoint )
 .then( function( savedPoint ) {
   console.log "geo point added " + savedPoint.geopoint.objectId );
   Backendless.Geo.deletePoint( result.geopoint )
    .then( function() {
     console.log( "geo point has been deleted" );
    })
   .catch( errorHandler ););
 .catch( errorHandler );

 

Adding a Geo Category

This API creates a geo category. A geo category is a logical grouping of geo points. Category name may contain the following literals: a-z, A-Z, numbers 0-9 and the underscore (_ ) character. The name must start with a literal. Category names can be inspected using Backendless Console (see the image below) or using the API call retrieving a list of categories.

 

Adding Categories in Console

Backendless Console supports adding a category via the graphical interface. To create a category:

1. Login to Backendless Console and select your app/backend.
2. Click the Geolocation icon in the menu on the left.
3. Use the "plus" icon located above the section containing the list of categories:
create-new-geo-cat
4. Enter the category name in the popup and click "Save".

 
Adding Categories with the API

Non-blocking method:

Backendless.Geo.addCategory( name )
 .then( function( addedCategory ) {
 })
 .catch( function( error ) {
 });

Blocking method:

var addedCategory = Backendless.Geo.addCategorySync( name );

where:

name - name of the category to create.

Return Value:

A javaScript object with the following structure:

{
  "objectId": "<categoryId>",
  "size": "0",
  "name": "<categoryName>"
}

where:

<categoryId> - internal ID assigned to the category.
<categoryName> - name of the category created with the request.
 

Example:

Backendless.Geo.addCategory( "mycategory" )
 .then( function( category ) {
   console.log( "category created - " + category.name );
  })
 .catch( function( error ) {
   console.log( "error - " + error.message );
  });

 

Deleting a Geo Category

This API deletes a geo category. If the category does not exist, the service returns an error.
 

Removing Categories in Console

Backendless Console supports category deletion via the graphical interface. To delete a category:

1. Login to Backendless Console
2. Select your app/backend.
3. Click the Geolocation icon in the menu on the left.
4. Use the "delete" icon in the section above the list of categories:
delete-geo-category
5. Select DELETE in the confirmation popup.

 
Deleting Categories with the API
Non-blocking method:

Backendless.Geo.deleteCategory( name )
 .then( function( deletionStatus ) {
 })
 .catch( function( error ) {
 });

Blocking method:

var deletionStatus = Backendless.Geo.deleteCategorySync( name );

where:

name - name of the category to delete.

Return Value:

true if the category is deleted, false otherwise.

Example:

Backendless.Geo.deleteCategory( "mycategory" )
 .then( function( deletionStatus ) {
  console.log( "category has been deleted - " + deletionStatus );
 })
 .catch( function( error ) {
  console.log( "error - " + error.message );
 });

 

Retrieving Geo Categories

This API retrieves a list of all the application's geo categories.

Non-blocking method:

Backendless.Geo.getCategories()
 .then( function( geoCategoriesArray ) {
 })
 .catch( function( error ) {
 });

Blocking method:

var geoCategoriesArray = Backendless.Geo.getCategoriesSync();

Return Value:

An array of JavaScript objects. Each object has the following structure:

{
  "objectId": "<categoryId>",
  "size": <categorySize>,
  "name": "<categoryName>"
}

where:

<categoryId> - ID assigned by Backendless to the category.
<categorySize> - number of geo points contained in the geo category.
<categoryName> - name of the category.

 

Example:

Backendless.Geo.getCategories()
 .then( function( geoCategoriesArray ) {
   console.log( "received " + geoCategoriesArray.length + " geo categories" );
 })
 .catch( function( error ) {
   console.log( "error - " + error.message );
 });

 

Get Geopoint Count

// TODO

Importing Geo Data

Backendless console supports bulk import of geo points with metadata. The import procedure automatically places the geo points into the specified categories. The raw data must be in a comma separated values (CSV) format. For more details about geopoint import see the Importing for Geo Service section of the guide.

Search in Category

This API supports two types of geo searches:

Search in one or more geo categories.
Search based on metadata properties in one or more categories .

 

Methods:

Non-blocking method:

Backendless.Geo.find( query )
 .then( function( geoPointsArray ) {
 })
 .catch( function( error ) {
 });

Blocking method:

var geoPointsArray = Backendless.Geo.findSync( query );

where:

query - a query object to run the search with. Must have the following structure:

{
  metadata: <metadataObj>,
  categories: <categoriesArray>,
  includeMetadata: <metaInResponse>,
  pageSize: <pageSize>,
  offset: <offset>
}

where:

<metadata> - metadata which must match in order for a point to be selected for the search result. Must be a JavaScript object. Backendless searches for geo points with metadata which matches the specified object entirely. See partial match search for the search API that does not require complete matches. Accepted values for this parameter are: String, Number (integer and double), and Data Service objects. Date values must be represented as number in the Unix timestamp format (number of milliseconds since January 1, 1970 at UTC). Learn more about using date in search queries for category, radius, or rectangular area search.
<categories> - list of categories separated by comma where to run the search. If the parameter is not present in the request, the search is ran in the "Default" category.
<metaInResponse> - a boolean value indicating whether geo point metadata should be included in the response.
<pageSize> - number of geo points to be returned in the response.
<offset> - sequential (zero-based) index from where to run the search. For example, suppose the first search query returned 50 geo points (pageSize is set to 50). A subsequent search should set the offset value to 50 in order to get the next page of search results.

Return Value:

Operation returns an array of geopoint objects. Each geopoint object has the following structure:

{
  categories: array of category names
  latitude: latitude of the geo point
  longitude: longitude of the geo point
  objectId: ID assigned by Backendless to the geo point
}

 

Since the search query may produce a large number of geo points, not all of them are returned at once. Instead, all found geo points are divided into 'pages'. The size of each page is determined by the pageSize parameter in the query object. The first response returns the first page. The query object includes methods for adjusting page size and offset. Subsequent search requests with the query object will return additional pages of data.

 

Calculating offset

All geo points in the entire search result are indexed. The index of the first geo point is 0. The offset parameter in the query object specifies the index from which to load the next page of geo points. For example, suppose the entire search result is 200 points. If the initial pageSize is 20, then only 20 geo points are returned in the first response. To get the second page of geo points, they should be loaded from offset 20, third from 40 and so on. The formula for calculating the offset is:
 [value of offset in the current response] + [size of current page ].

 

Running Search Queries

The geo query object includes multiple parameters. Depending on which parameters contain values, the semantics of the search would change. Any search must be performed within at least one category. If no category names are provided, the search is performed in the Default category.

 

Search in categories

To search in one or more categories without any constraints on metadata or proximity to a center point, simply set the names of the categories in the query object. The request returns all geo points divided into pages of data, one page at a time.

var geoQuery = {
  categories: ["Restaurants"]
}

Backendless.Geo.find( geoQuery )
 .then( function( geoPointsArray ) {
    console.log( "found geo points - " + geoPointsArray.length );
 })
 .catch( function( error ) {
    console.log( "error - " + error.message );
 })

Search in categories with metadata

Metadata-based search finds all geo points which match all specified metadata properties in the given categories. The example below searches for the geo points in the Restaurants category with metadata containing "Cuisine = French" and "Atmosphere = Romantic".

var geoQuery = {
  metadata: {Cuisine:"French", Atmosphere:"Romantic"},
  categories: ["Restaurants"]
}

Backendless.Geo.find( geoQuery )
 .then( function( geoPointsArray ) {
    console.log( "found geo points - " + geoPointsArray.length );
 })
 .catch( function( error ) {
    console.log( "error - " + error.message );
 })

 
 

Using dates in where clause when searching in categories

The search query used to retrieve geo points may reference date values. These values must be stored as a number of milliseconds since January 1st, 1970 at UTC. The example below demonstrates the usage of a date/time timestamp in a search query:

var query = new Backendless.GeoQuery(); 
query.categories = ['Coffee']; 
query.includeMetadata = true; 
query.condition = "updated > " + updated; 

Backendless.Geo.find(query)
 .then( function( geoPointsArray ) {
    console.log( "found geo points - " + geoPointsArray.length );
 })
 .catch( function( error ) {
    console.log( "error - " + error.message );
 })

 

Requesting meta in response

Geo points returned in the search results do not include their metadata properties by default. The search query object includes a property which can be used to request the metadata to be included. This property can be used with any search options described above. For example, the following code runs a search in a category and requests the metadata to be included:

var geoQuery = {
  includeMetadata:true,
  categories: ["Restaurants"]
}

Backendless.Geo.find( geoQuery )
 .then( function( geoPointsArray ) {
    console.log( "found geo points - " + geoPointsArray.length );
 })
 .catch( function( error ) {
    console.log( "error - " + error.message );
 })

 

Search in Radius

This API supports multiple types of geo searches:

Search for geo points located within specified distance (radius) from a given point.
Search in radius based on metadata.

 

Non-blocking method:

Backendless.Geo.find( query )
 .then( function( geoPointsArray ) {
 })
 .catch( function( error ) {
 });

Blocking method:

var geoPointsArray = Backendless.Geo.findSync( query );

where

query - query object to run the search with. Must have the following structure:

{
  radius: <radius>,
  latitude: <latitude>,
  longitude: <longitude>,
  units: <units>,
  metadata: <metadataObj>,
  categories: <categoriesArray>,
  includeMetadata: <metaInResponse>,
  pageSize: <pageSize>,
  offset: <offset>
}

where:

<radius> - distance from the center point within which to run the search.
<latitude> - latitude of the point in the center of the search.
<longitude> - longitude of the point in the center of the search.
<units> - unit of measure applied to the radius value. Supported unit values are: METERS, KILOMETERS, MILES, YARDS, FEET
<metadata> - metadata which must match in order for a point to be selected for the search result. Must be a JavaScript object. Backendless searches for geo points with metadata which matches the specified object entirely. See partial match search for the search API that does not require complete matches. Accepted values for this parameter are: String, Number (integer and double), and Data Service objects. Date values must be represented as number in the Unix timestamp format (number of milliseconds since January 1, 1970 at UTC). Learn more about using date in search queries for category, radius, or rectangular area search.
<categories> - list of categories separated by comma where to run the search. If the parameter is not present in the request, the search is ran in the "Default" category.
<metaInResponse> - a Boolean value indicating whether geo point metadata should be included in the response.
<pageSize> - number of geo points to be returned in the response.
<offset> - sequential (zero-based) index from where to run the search. For example, suppose the first search query returned 50 geo points (pageSize is set to 50). A subsequent search should set the offset value to 50 in order to get the next page of search results.

 

Return Value:

Operation returns an array of geopoint objects. Each geopoint object has the following structure:

{
  categories: array of category names
  latitude: latitude of the geo point
  longitude: longitude of the geo point
  objectId: ID assigned by Backendless to the geo point
}

 

Since the search query may produce a large number of geo points, not all of them are returned at once. Instead, all found geo points are divided into 'pages'. The size of each page is determined by the pageSize parameter in the query object. The first response returns the first page. The query object includes methods for adjusting page size and offset. Subsequent search requests with the query object will return additional pages of data.

 

Calculating offset

All geo points in the entire search result are indexed. The index of the first geo point is 0. The offset parameter in the query object specifies the index from which to load the next page of geo points. For example, suppose the entire search result is 200 points. If the initial pageSize is 20, then only 20 geo points are returned in the first response. To get the second page of geo points, they should be loaded from offset 20, third from 40 and so on. The formula for calculating the offset is:
 [value of offset in the current response] + [size of current page ].

 

Running Search Queries

The geo query object includes multiple parameters, none of them are required. As a result, depending on which parameters contain values, the semantics of the search would change. Any search must be performed within at least one category. If no category names are provided, the search is performed in the Default category.

 

Search in categories with radius

Radius-based search establishes a circular area by setting the coordinates of a central point and a distance (radius). Backendless searches for geo points in the specified distance from the coordinates in the center and includes them into the search result. The value of the distance is interpreted based in the units parameter, which can be METERS, KILOMETERS, MILES, YARDS, FEET:

var geoQuery = {
  latitude: 41.38,
  longitude: 2.15,
  radius: 100000,
  units: "METERS",
  categories: ["Restaurants"]
}

Backendless.Geo.find( geoQuery )
 .then( function( geoPointsArray ) {
    console.log( "found geo points - " + geoPointsArray.length );
 })
 .catch( function( error ) {
    console.log( "error - " + error.message );
 })

Search in categories with radius and metadata

This is the same as above, with the difference that the search result includes only geo points with the matching metadata:

var geoQuery = {
  latitude: 41.38,
  longitude: 2.15,
  radius: 100000,
  units: "METERS",
  metadata: {Cuisine:"French", Atmosphere:"Romantic"},
  categories: ["Restaurants"]
}

Backendless.Geo.find( geoQuery )
 .then( function( geoPointsArray ) {
    console.log( "found geo points - " + geoPointsArray.length );
 })
 .catch( function( error ) {
    console.log( "error - " + error.message );
 })

 

Using dates in where clause when searching in radius

The search query used to retrieve geo points may reference date values. These values must be stored as a number of milliseconds since January 1st, 1970 at UTC. The example below demonstrates the usage of a date/time timestamp in a search condition:

var query = new Backendless.GeoQuery(); 
query.categories = ['Coffee', 'City']; 
query.includeMetadata = true; 
query.latitude = 21.30; 
query.longitude = -157.858333; 
query.radius = 50; 
query.units = Backendless.Geo.UNITS.KILOMETERS; 
// deduct 1 day worth of milliseconds: 86400000 
query.condition = "updated > " + Date.now() - 86400000;  

Backendless.Geo.find(query) 
 .then( function( geoPointsArray ) {
    console.log( "found geo points - " + geoPointsArray.length );
 })
 .catch( function( error ) {
    console.log( "error - " + error.message );
 })

 

Requesting meta in response

Geo points returned in the search results do not include their metadata properties by default. The search query object includes a property which can be used to request the metadata to be included. This property can be used with any search options described above. The syntax for requesting metadata in response is described in the Search in Category section.

Search in Rectangular Area

This API runs a search within a rectangular area of the map. The area is defined with the coordinates of the North West and South East corners of the map rectangle.

Non-blocking method:

Backendless.Geo.find( query )
 .then( function( geoPointsArray ) {
 })
 .catch( function( error ) {
 });

Blocking method:

var geoPointsArray = Backendless.Geo.findSync( query );

where

query - query object to run the search with. Must have the following structure:

{
  searchRectangle: <search-rect-coord>,
  metadata: <metadataObj>,
  categories: <categoriesArray>,
  includeMetadata: <metaInResponse>,
  pageSize: <pageSize>,
  offset: <offset>
}

where:

<search-rect-coord> - an array of 4 numbers which are the coordinates defining the search area. The numbers must be in the following order:

[
  North West latitude, 
  North West longitude, 
  South East latitude, 
  South East longitude 
]

<metadata> - metadata which must match in order for a point to be selected for the search result. Must be a JavaScript object. Backendless searches for geo points with metadata which matches the specified object entirely. See partial match search for the search API that does not require complete matches. Accepted values for this parameter are: String, Number (integer and double), and Data Service objects. Date values must be represented as number in the Unix timestamp format (number of milliseconds since January 1, 1970 at UTC). Learn more about using date in search queries for category, radius, or rectangular area search.
<categories> - list of categories separated by comma where to run the search. If the parameter is not present in the request, the search is ran in the "Default" category.
<metaInResponse> - a Boolean value indicating whether geo point metadata should be included in the response.
<pageSize> - number of geo points to be returned in the response.
<offset> - sequential (zero-based) index from where to run the search. For example, suppose the first search query returned 50 geo points (pageSize is set to 50). A subsequent search should set the offset value to 50 in order to get the next page of search results.

Return Value:

Operation returns an array of geopoint objects. Each geopoint object has the following structure:

{
  categories: array of category names
  latitude: latitude of the geo point
  longitude: longitude of the geo point
  objectId: ID assigned by Backendless to the geo point
}

 

Since the search query may produce a large number of geo points, not all of them are returned at once. Instead, all found geo points are divided into 'pages'. The size of each page is determined by the pageSize parameter in the query object. The first response returns the first page. The query object includes methods for adjusting page size and offset. Subsequent search requests with the query object will return additional pages of data.

 

Calculating offset

All geo points in the entire search result are indexed. The index of the first geo point is 0. The offset parameter in the query object specifies the index from which to load the next page of geo points. For example, suppose the entire search result is 200 points. If the initial pageSize is 20, then only 20 geo points are returned in the first response. To get the second page of geo points, they should be loaded from offset 20, third from 40 and so on. The formula for calculating the offset is:
 [value of offset in the current response] + [size of current page ].

Running Search Queries

The geo query object includes multiple parameters, however, only the coordinates defining the rectangular area are required.  A search query must be performed within at least one category. If no category names are provided, the search is performed in the Default category.

 

Search in a rectangle in categories

Rectangle-based search establishes a geographic area by setting the coordinates of the North West and South East corners of the area. Backendless searches for geo points in the specified area and includes them into the search result:

var geoQuery = {
  searchRectangle: [32.78, -96.8, 25.79, -80.22],
  categories: ["Restaurants"]
}

Backendless.Geo.find( geoQuery )
 .then( function( geoPointsArray ) {
    console.log( "found geo points - " + geoPointsArray.length );
 })
 .catch( function( error ) {
    console.log( "error - " + error.message );
 })

Search in categories in a rectangular area and metadata

This is the same as above, with the difference that the search result includes only geo points with the matching metadata:

var geoQuery = {
  searchRectangle: [32.78, -96.8, 25.79, -80.22],
  metadata: {Cuisine:"French", Atmosphere:"Romantic"},
  categories: ["Restaurants"]
}

Backendless.Geo.find( geoQuery )
 .then( function( geoPointsArray ) {
    console.log( "found geo points - " + geoPointsArray.length );
 })
 .catch( function( error ) {
    console.log( "error - " + error.message );
 })

Using dates in where clause

The search query used to retrieve geo points may reference date values. These values must be stored as a number of milliseconds since January 1st, 1970 at UTC. The example below demonstrates the usage of a date/time timestamp in a search query:

var now = dateParser(new Date(2015, 0, 17, 15, 19)); 

var query = new Backendless.GeoQuery(); 
query.categories = ['Coffee']; 
query.includeMetadata = true; 
query.condition = "opened < " + now + " AND closed > " + now; 
query.searchRectangle = [21.306944 + 0.5, 
                         -157.858333 - 0.5, 
                         21.306944 - 0.5, 
                         -157.858333 + 0.5]; 
Backendless.Geo.find(query)
 .then( function( geoPointsArray ) {
    console.log( "found geo points - " + geoPointsArray.length );
 })
 .catch( function( error ) {
    console.log( "error - " + error.message );
 })

Requesting meta in response

Geo points returned in the search results do not include their metadata properties by default. The search query object includes a property which can be used to request the metadata to be included. This property can be used with any search options described above. The syntax for requesting metadata in response is described in the Search in Category section.

GeoPoint Clustering

Geo point search in a category, radius or a rectangular area may return too many geo points within close geographic proximity from each other. This might be difficult to view and process in a client application. To address this problem, Backendless supports the geo clustering feature. A geo cluster is a group of several geo points located close to each other. The two screenshots below demonstrate the advantages of clustering: the picture on the top displays search results in the Backendless Console with clustering turned off and the one on the bottom displays search results as clusters when clustering has been enabled:

 

Geo Points View:

clustering-points.zoom50

 

Clusters and Points View:

clustering-clusters.zoom50

 

Backendless creates clusters by splitting the map into a grid of squares. Geo points which belong to a square are placed into the same cluster. When a square contains only one point, it remains non-clustered.

 

Testing Geo Clustering in Backendless Console

The Geolocation page displays non-clustered geo points by default. To see how geoclustering works and test geoclustering search results:

1. Log in to Backendless Console, select an application and click the Geolocation icon.
2. Click the Map-driven navigation toggle. The toggle changes how the geo points are loaded from the backend. In the map-driven mode console loads the geo points for the visible area of the map.
3. Click the Geo Clustering toggle to enable clustering.
4. Console reloads geo points and clusters for the current viewport of the map and displays the results. A cluster is visualized as a blue marker on the map with a number indicating how many geo points it represents.
geofence-screen7.zoom50

 

Geo clustering is also available with the "Search in Radius" option, which searches for geo points in a circular area. To enable this functionality, click the Search in radius toggle:

geofence-screen8.zoom50

 

If you want to see the geo points in a cluster, zoom in the map or double-click a cluster's marker. Zooming the map in too much (when using the clustering along with the search in radius) may result that the search radius will be much bigger than the visible part of the map on the screen. In this case, zoom out so you can adjust the circle position and/or radius.

 

Clicking a cluster's marker will reveal the coordinates and combined metadata of all geopoints in the cluster.

geofence-screen9.zoom50

Retrieving Clustered Geo Points

Geo point clustering can be requested by setting clustering parameters in Backendless.GeoQuery, which is used in the calls to retrieve geo points from a category, radius or rectangular area.

 

geoQueryObject.setClusteringParams(westLongitude, 
                                   eastLongitude, 
                                   mapWidth, 
                                   clusterGridSize )  

// same as above, but uses the default clusterGridSize of 100
geoQueryObject.setClusteringParams(westLongitude, 
                                   eastLongitude, 
                                   mapWidth ).

where

westLongitude - the longitude of any point on the western boundary of the map in degrees.
eastLongitude - the longitude of any point on the eastern boundary of the map in degrees.
mapWidth - the size of the viewing area of the map in pixels.
clusterGridSize - the size in pixels of the grid's squares used to group points into clusters. The default value is 100 pixels

Example:

Once the clustering parameters are set, the geo point search API will return clustered geo points. The return value is a collection of GeoCluster and/or GeoPoint objects. Instances of the latter may be returned when a geo point does not have any neighboring points within the grid's square it belongs to. The GeoCluster class extends from GeoPoint and supports all the inherited properties: latitude, longitude, categories and metadata. Additionally, geo cluster has its own property representing the number of points the cluster consists of. Since a GeoCluster object an instance of the GeoPoint class, the processing of search responses may look like this:

var geoQuery = new Backendless.GeoQuery();
geoQuery.categories = ["geoservice_sample"];
geoQuery.pagesize = 50;
geoQuery.offset = 0;
geoQuery.includeMetadata = true;
geoQuery.setClusteringParams(-142.925, -51.343, 1024, 1000);

Backendless.Geo.find(geoQuery)
 .then( function( geoPointsAndClusters ) {
     var counterCluster = 0;
     var counterTotalPoints = 0;
     var counterPoints = 0;
     var number = 0;
     for(var i = 0; i < geoPointsAndClusters.length; i++){
       number = i + 1;
       if(geoPointsAndClusters[i] instanceof Backendless.GeoCluster){
         console.log("Object" + number +" is geo-cluster, it contains: " + 
                     geoPointsAndClusters[i].length + " points;");
         counterTotalPoints += geoPointsAndClusters[i].length;
         counterCluster ++;
       }
       else if(geoPointsAndClusters[i] instanceof Backendless.GeoPoint){
         console.log("Object" + number +" is geo-point, it contains: 1 point;");
         counterTotalPoints += 1;
         counterPoints ++;
       }
     }
     console.log("Received collection contains " + counterTotalPoints + " points.");
     console.log( counterCluster + " objects type of GeoCluster and " + 
                  counterPoints + " objects type of GeoPoint.");
 })
 .catch( function( error ) {
   console.log( "error - " + error.message );
 });

Loading Geo Points from a Cluster

With the geo clustering feature enabled, you may need to reveal the geo points gathered in a cluster. In the Backendless Console, you can click the cluster to reveal its details as described above. By using the calls described below, you will be able to reveal the geo points in a cluster via API.

Non-blocking method:

Backendless.Geo.getClusterPoints( geoCluster )
 .then( function( geoPointsArray ) {
 })
 .catch( function( error ) {
 });

Blocking method:

var geoPointsArray = Backendless.Geo.getClusterPointsSync( geoCluster );

Return Value:

An array of GeoPoint objects for the geocluster.

 

Relations with Data Objects

Backendless Data Service manages application's data objects and provides APIs to work with data objects. Backendless provides integration between data objects managed by Data Service and geo points managed by Geo Service for the scenarios when a logical connection between the two types must exist in an application. For instance, in a taxi ordering app a data object may represent a taxi car, while a geo point represents its location on the map. It is logical to link the two together so they can be retrieved and managed at once.

The Geo-to-Data integration is implemented through geo point metadata. A metadata property may reference one or more data objects. These relations may be created using the API or with Backendless Console. Once a relation is established, the console displays it in the Metadata column as a link to related data object(s). When a geo point is retrieved using the API, any related data objects can be retrieved by using the same principle for loading geo point metadata. The geo-to-data relation is bidirectoral, that is, a data object may reference a geo point through object properties (table columns). You can learn more about it in the Relations with Geo Points section of the Data documentation.

 

Apart from linking with the data objects, you can also link a geo point with a user object. Establishing relations with a user objects is performed the same way as with a data object.

 

Establishing Relations with a Data Object via Console

To link a geo point with a data object:

1. Click the Geolocation icon to open the Geo location screen.
2. Select a geo category to get a list of geo points from it.
3. Click the plus icon for a geo point you want to link with a data object.
geofence-screen10.zoom50

 

4. The Add Related Object pop-up window will display.
5. Type in a metadata property name in the Property field. This is the name of a new metadata property which will point to a related data object:
geofence-screen11.zoom70
6. Select a data table from from the Table drop-down menu. If you want to establish relation with a user object, select the Users  table from the drop-down menu. A list of the data objects which belong to the selected table will display.
7. Select the check-boxes for the data object(s) you want to link the geo point with.
8. Click the Add Related Objects button to establish a relation and save the changes.

 

Once a relation is established, the name of the property and the related data table name will display for the geo point:

geofence-screen12.zoom50

Updating/Deleting Relations

You can update or delete a geo to data relation by following the same process as above. The only difference is in order to access the"relation management" popup click the yellow box identifying the relation:

Create a Geo to Data Relation with the API

Creating a relationship between a geo point and data objects uses the same API as saving a geo point with metadata. The data object referenced in the metadata is saved in a table in the Data Service storage.

 

The example below adds a geo point representing a location of a taxi cab. The geo point includes the "TaxiCab" metadata property which references an object from the TaxiCab data table. This is an example of a one-to-one relation (one geo point is related to one data object).

Create a one-to-one relation:
var cab = { make:"Ford", model:"Crown Victoria", ___class:"TaxiCab" };
var pickupLocation = new Backendless.GeoPoint();
pickupLocation.latitude = 40.750549;
pickupLocation.longitude = -73.994232;  
pickupLocation.categories = [ "Pickups" ];
pickupLocation.metadata = { TaxiCab : cab };
Backendless.Geo.savePoint( pickupLocation )
 .then( function( savedGeoPoint ) {
   console.log( "geo point with data object has been saved" );
 })
 .catch( function( error ) {
   console.log( "error - " + error );
 });
Or a one-to-many relation:
var cab1 = { make:"Ford", model:"Crown Victoria", ___class:"TaxiCab" };
var cab2 = { make:"Toyota", model:"Prius", ___class:"TaxiCab" };
var availableCabs = [ cab1, cab2 ];

var pickupLocation1 = new Backendless.GeoPoint();
pickupLocation1.latitude = 40.750549;
pickupLocation1.longitude = -73.994232;
pickupLocation1.categories = [ "Pickups" ];
pickupLocation1.metadata = { AvailableCabs: availableCabs };
Backendless.Geo.savePoint( pickupLocation1 )
 .then( function( savedGeoPoint ) {
    console.log( "geo point with data object has been saved" );
 })
 .catch( function( error ) {
    console.log( "error - " + error.message );
 });
First, declare the TaxiCab class/function:
function TaxiCab (args) 
{
    args = args || {};
    this.CarMake = args.CarMake || null;
    this.CarModel = args.CarModel || null;
    this.Location = new Backendless.GeoPoint();
    this.PreviousDropOffs = [];
    this.___class = "TaxiCab";
}
Create a one-to-one relation:
var cab = new TaxiCab();
cab.CarMake = "Ford";
cab.CarModel = "Crown Victoria";

var pickupLocation = new Backendless.GeoPoint();
pickupLocation.latitude = 40.750549;
pickupLocation.longitude = -73.994232;
pickupLocation.categories = ["Pickups"];
pickupLocation.metadata = {"TaxiCab": cab};
Backendless.Geo.addPoint(pickupLocation);
Or a one-to-many relation:
var cab1 = new TaxiCab();
cab1.CarMake = "Ford";
cab1.CarModel = "Crown Victoria";

var cab2 = new TaxiCab();
cab2.CarMake = "Toyota";
cab2.CarModel = "Prius";

var availableCabs = [ cab1, cab2 ];

var pickupLocation1 = new Backendless.GeoPoint();
pickupLocation1.latitude = 40.750549;
pickupLocation1.longitude = -73.994232;
pickupLocation1.categories = ["Pickups"];
pickupLocation1.metadata = {"AvailableCabs": availableCabs};
Backendless.Geo.addPoint(pickupLocation1);

Geofence Designer

About Geofencing

Geofencing on the surface, comprises drawing a stationary geometric boundary around an area on a map. This action creates programmatically a set of simple or complex global coordinates which represent a shape. A boundary represents a "fence," which surrounds the area. For Backendless, the boundary and area become meaningful when a Geopoint crosses the boundary or stays within the area.

 

Geofences work with Geopoints. A Geopoint is the most elementary Geolocation concept in Backendless. It is a point (latitude and longitude coordinate pairs) on the map that is accessible via API and allowed to move (change coordinates), i.e., a user carrying a mobile device. In addition to the coordinates, the Geopoint includes metadata in context for the Geopoint.

 

The Geofence Designer

Geofence Designer is a feature of the Geolocation service available under Geolocation > Geofencing. It is a "design time" tool for drawing Geofences on an interactive global map and associating them with events and actions, which can be triggered based on the location of registered Geopoints. Backendless integrates the designer with Google MapsTM, enabling the developer to design precise Geofence positions, locations, and shapes.

 

The Design Tool includes line, rectangle, and circle drawing tools (the cursor changes from hand to crosshairs when selected) for creating Geofence boundaries. Boundaries can be geometrically symmetrical or irregular shapes and have no minimum or maximum size constraints. In use cases, a Geofence conceptually "fences in" a city block, a shopping center, a sports stadium, or perhaps a mall; even smaller geographic areas are possible such as the shoe department in a retail store.

 

fencing11.zoom80

The Line Tool

To define an irregular Geofence, the line tool draws editable line segments and control points. For example, in the United States, the shape of Nevada is irregular. To create this shape, select the line tool to start drawing lines around it.

 

Place the cursor on the map where the first control point should be and click. Drag the cursor to the next place and Click again. The first line segment appears. Repeat these steps until you have nearly completed the shape of your boundary. (It's not a Geofence just yet.) Click the cursor on the last control point (which was the first one set). Backendless detects a closed shape and enables a new Geofence.

 

NOTE: If you accidentally close the Geofence before completing the drawing, the New Geofence dialog box appears. If you click Cancel, the Geofence will be removed. To keep the Geofence, click Save, then re-edit the shape as needed.

geofence-screen1.zoom50

 

Immediately after the shape closes, a popup appears prompting you to name the Geofence. Enter a name in the Geofence Name text box. Since this example uses the state of Nevada, it makes sense to name it Nevada. Click Save to enable the Geofence. (We will refer to this example again.)

geofence-screen2

 

The result is a new item row in the List of Geofences. The Geofence area is filled with green, and the item row is highlighted in yellow when a Geofence is selected on the map or on the list. See the image below.

geofence-screen3.zoom80

The Rectangle Tool

The Rectangle Tool is self-describing. A fence can be drawn quickly around a square or rectangular area. After the Geofence is named and saved, the shape aspect can be adjusted by dragging a line segment or corner control point.  Like the line tool, parameters can be entered in a dialog immediately after the shape is drawn. Should you need to edit the shape, an undo tool appears and to restore the previous edit.

 

fencing13

The Circle Tool

The Circle Tool is also self-describing. A circle can be drawn quickly around an area, repositioned, and resized. Like the line tool, parameters can be entered in a dialog immediately after the shape is drawn.

Deleting a Geofence

A Delete button is positioned directly below the interactive map. For each selected checkbox next to the Geofence name, the Delete button removes those Geofences. Once a Geofence is deleted, it cannot be restored.

geofence-screen4

List of Geofences and Locator Tool

A map filled with Geofences can appear cluttered, especially if the design comprises numerous shapes across several remotely located areas. Backendless organizes the boundary data and actions in a table format below the map, the List of Geofences. The table contains a row for each Geofence along with parameter controls.

 

From left to right, the second column shows the Geofence name, which is hyperlinked to the Update Geofence dialog. The Geofence locator icon is next to the Geofence hyperlink (see image below). The tool repositions the  map view to an optimal zoom-level, from which the Geofence boundary can be easily viewed, accessed, and edited.

geofence-screen5.zoom60

Geofence and Geopoints Events

Geofences and Geopoints are integrated entities of Geolocation. As such, Backendless tracks three specific events:

 

When a Geopoint enters a Geofence, crosses the boundary, On Enter action executes.
When a Geopoint stays inside a shape for a preconfigured amount of time, On Stay actions executes.
When a Geopoint exits the Geofence, it crosses the boundary and is outside the shape, On Exit action executes.

Geopoint Qualification Criteria: Exclusion/Inclusion

Tracking every Geopoint within a Geofence is not desirable in every case. A Geofence plan could specify tracking only Geopoints of a certain nature, for example, visitors or preferred customers.

 

Qualification Criteria, which identifies the types of Geopoints Backendless traces and tracks for a specific geogence, can be defined in the Update Geofence dialog. The Geofence hyperlink opens the dialog. A criterion, in this case, is a special string entered in the Geopoint Qualification Criteria text box. (The string format is the SQL 92 syntax, regular SQL as relates to a database query WHERE clause.) For example, if tracking only visitors, the SQL would need something like usertype=visitor. Where usertype is a Geopoints metadata property. visitor is the metadata property value.

geofence-screen6

Events and Actions

Detecting, tracing, and tracking geopoints in relationship to a geofence establish the event clockwork for execution of developer defined actions. An event occurs as a Geopoint transports into and out of or stays in a Geofence. An action is a set of parameters the developer selects to perform a function, such as deliver a message to a mobile device or add a record to a database.

Events

Three event types are organized in columns in the List of Geofences. The events types are:

 

On Enter - a Geopoint crosses the Geofence boundary into the defined area
On Stay - a Geopoint remains in the Geofence area for at least a specified period
On Exit - a Geopoint crosses the Geofence boundary out of the defined area

Actions

For each of the above events, a developer can select an action and specify parameters to be executed from Backendless. There are four action types:
 

Push notification
Publish-subscribe (pub/sub) message
Send a custom event
Call a URL

 

The scenarios for choosing an action are wildly different; however, they drive the action and parameter choices the developer makes. When an action type is selected for an event, a dialog appears where action parameters can be entered. The fields in the dialog are specific to the action type.

 

fencing-17.zoom30

fencing-18.zoom30

fencing-19.zoom30

fencing-20.zoom30

 

Whenever an action is configured, visual elements indicate whether the parameters are complete. A gear icon and green checkmark indicate proper configuration, where as a red X in place of the checkmark indicates improper configuration. The configuration can be edited by clicking the gear to reopen the currently assigned action dialog.

 

Push Notification Action

All three Geofence events can trigger this action. Push Notification, in basic form, is a message sent to a mobile device associated with a Geopoint or to a group of devices registered with a channel. The Configure Push Notification Action dialog provides flexible parameter options:

 

Content Configuration - configure Push Notification content look and feel for Android, iOS, or Windows Phone.
Message Headers - allows header name and header value.
Delivery - to individual Geopoints or those registered to a channel.

 

fencing-17

Send a Pub/Sub Message Action

This action sends a publish/subscribe message. The developer enters the message parameters in the Configure Pub/Sub Message Action dialog. (Learn more about Message Publishing.) The dialog contains the following fields:

 

Channel name - the name for a channel. Backendless creates the channel if it doesn't exist.
Topic name - the name of a topic used for filtered delivery.
Message headers - optional. Use the key=value format. Comma delimited.
Message body - written in JSON. The body of the message to be delivered.

fencing-18

 

_img2

A probation officer (PO) issues an ankle bracelet to a probationer. He needs to set area from which the probationer cannot leave, such as restricted to travel only within a state. The PO would set up a Geofence outlining Colorado's state-line borders. For that Geofence, he would set the action event On Exit to send a notification to the application on his device that would provide metadata about the probationer such as location, the probationer's photo, phone number, address, etc.

 

Call URL Action

This action executes an HTTP command on the specified URL. Supported commands are GET, POST, or PUT. The developer configures the call in the Configure Call URL Action dialog. The dialog contains the following fields:

 

Command - choice of GET, POST, or PUT
URL - a fully formed internet protocol URL.
Request headers - any of the HTTP header types in the form of key=value.
Body - text message for calling the URL.

fencing-20

Send Custom Event

Provides a means for enabling Geofencing processing logic to execute your custom server-side code deployed to Backendless. Send Custom Event issues an event that you define in the Send Custom Event dialog box. See Custom Events under Business Logic. Backendless acts as a functional intermediary for your custom event handler which contains business logic specific to the application such as sending out an email or saving a record to a database.

fencing-19

Is Active - Geofence Monitoring

Is Active is the final column in the List of Geofences. It indicates whether server-side Geofence monitoring can be activated. When monitoring is ON, Backendless tracks any movements of the Geopoints in relation to the corresponding Geofence. The list below shows item content:

 

Missing Actions - look for red X next to the edit action gear. An action is improperly configured.
ON/OFF Toggle - click to switch either on or off. ON activates the server-side monitoring for the selected Geofence.

 

When an action is properly configured, i.e. complete, the Is Active toggle for the selected Geofence appears. When set to ON, a popup provides cautionary information and a checkbox option, which applies actions to Geopoints located within the Geofence at the time when the monitoring is turned on (i.e. the toggle is being set to ON). See the image below.

fencing-21

 

Once server-side monitoring is activated by setting the Is Active toggle to ON, a play button appears next to the gear icon. This button executes the action on-demand for any Geopoints within the Geofence. (This function can be useful when debugging.)

View Geopoints in a Geofence

For any Geofence that is currently active (i.e. is under server-side monitoring), a checkbox option displays Geopoints located within the Geofence. Geopoints located within the Geofence  are represented by a marker for Google Maps inside the Geofence.

 

The frequency of refresh on the screen is controlled by the refresh interval. The data is being refreshed every 10 seconds. You can force a refresh by clicking the refresh button. See the image below.

fencing-22

 

Geofence API

Client-side geofence monitoring is the process of tracking the position of the device in relation to the geofences defined in Backendless. Functionality  in Backendless client libraries loads information about geofences (or a specific geofence) and tracks device positional changes. When the device crosses, stays in, or exits a geofence boundary, the Backendless client executes a callback. Based on this, Backendless client-side monitoring supports the following options:

 

In-app callback for the on enter, on stay and on exit events. A callback is executed on the device when it's location enters, stays in or exits a geofence. With this approach the client application decides how to handle a geofence event.
hmtoggle_plus1  In-app callback interface

Client applications must use a special class to receive in-app callbacks. The class includes delegate methods invoked by the Backendless library when the current device's location enters, stays in or exits a geofence. The callback's delegate methods include information about the current location.

var inAppCallback = { 
    onenter: function ( geofenceName, geofenceId, latitude, longitude ) 
    { 
       ........ 
    }, 
    onstay: function( geofenceName, geofenceId, latitude, longitude ) 
    { 
      ......... 
    }, 
    onexit: function( geofenceName, geofenceId, latitude, longitude ) 
    { 
      ........ 
    } 
};

 

Consider the following example of a callback implementation. The example creates and saves a geopoint with dummy coordinates (0,0). As the device changes its location, the in-app callback is executed and the code saves the device's location on the server. See the example below.

var me = {
 latitude: 0,
 longitude: 0
}

Backendless.Geo.addPoint( me )
 .then( function( addedPoint ) {
   me = addedPoint;
 })
 .catch( function( error ) {
   console.log( "error - " + error.message );
 }); 
 
var inAppCallback = { 
    onenter: function ( geofenceName, geofenceId, latitude, longitude ) 
    { 
        me.latitude = latitude; 
        me.longitude = longitude; 
        Backendless.Geo.addPoint( me ); 
    }, 
    onstay: function( geofenceName, geofenceId, latitude, longitude ) 
    { 
        me.latitude = latitude; 
        me.longitude = longitude; 
        Backendless.Geo.addPoint( me ); 
    }, 
    onexit: function( geofenceName, geofenceId, latitude, longitude ) 
    { 
        me.latitude = latitude; 
        me.longitude = longitude; 
        Backendless.Geo.addPoint( me ); 
    } 
}; 

Remote callback for the on enter, on stay and on exit event. The Backendless client automatically notifies the server when a geofence event occurs. If there is an action associated with the event, Backendless executes on the server-side.

 

The API applies the options above either to a specific or all geofences. It is important to note that a Backendless client can monitor only the geofences which are not activated for the server-side monitoring. In other words, a geofence with the "Is Active" toggle in the Backendless console set to ON cannot be tracked on the client-side.

 

JavaScript App Configuration


TODO..

 

Geofence APIs


The Geolocation service provides the following APIs:

Client-side location monitoring:

Executing a geofence action:

Retrieve geopoints from a geofence

Start location monitoring with an in-app callback


Starts client-side monitoring of the device's location either for all or a specific geofence. Uses the in-app callback to notify when the device enters, stays in, or exits the geofence boundary.

Non-blocking method:

Backendless.Geo.startGeofenceMonitoringWithInAppCallback( geofenceName, inAppCallback )
 .then( function() {
 })
 .catch( function( error ) {
 });

Blocking method:

Backendless.Geo.startGeofenceMonitoringWithInAppCallbackSync( geofenceName, inAppCallback );

where:

geofenceName - name of a geofence for which to monitor the device's location. If the value of the argument is null, the Backendless client will monitor device's current location for all geofences.
inAppCallback - a callback object that the Backendless library notifies when the device crosses, stays in, or exits the geofence boundary.

Example:

// see the introduction to this section to understand how to initialize "me"

var inAppCallback = { 
    onenter: function ( geofenceName, geofenceId, latitude, longitude ) 
    { 
        me.latitude = latitude; 
        me.longitude = longitude; 
        Backendless.Geo.addPoint( me ); 
    }, 
    onstay: function( geofenceName, geofenceId, latitude, longitude ) 
    { 
        me.latitude = latitude; 
        me.longitude = longitude; 
        Backendless.Geo.addPoint( me ); 
    }, 
    onexit: function( geofenceName, geofenceId, latitude, longitude ) 
    { 
        me.latitude = latitude; 
        me.longitude = longitude; 
        Backendless.Geo.addPoint( me ); 
    } 
};  
  
Backendless.Geo.startGeofenceMonitoringWithInAppCallback( "Shopping Mall", inAppCallback )
 .then( function() {
   console.log( "Geofence monitoring has been started" ); 
 })
 .catch( function( error ) {
   console.log( "error - " + result.message );
 });

Start location monitoring with a remote callback


Starts client-side monitoring of the device's location for either for a specific or all geofences. This function notifies the server when the device enters, stays in, or exits the geofence boundary.

Non-blocking method:

Backendless.Geo.startGeofenceMonitoringWithRemoteCallback( geofenceName, geoPoint )
 .then( function() {
 })
 .catch( function( error ) {
 });

Blocking method:

Backendless.Geo.startGeofenceMonitoringWithRemoteCallbackSync( geofenceName, geoPoint );

where:

geofenceName - name of a geofence for which to monitor the device's location. If the value of the argument is null, the Backendless client will monitor device's current device location for all geofences.
geoPoint - the geopoint object to pass to the server. The geopoint represents the current device location. It may be a geopoint stored in the Geolocation storage (with objectId assigned to it) or a new geopoint object. The Backendless client assigns the current location coordinates to the geoPoint object before sending it to the server.

Example:

// Create a geopoint. The coordinates are irrelevant
// because Backendless client will update them when the 
// current location of the device where this program runs changes.
// The geopoint with the updated coordinates will be delivered 
// to the server when the the current location triggers a 
// geofence event;
var myLocation = {
 latitude: 0,
 longitude: 0
}
  
Backendless.Geo.startGeofenceMonitoringWithRemoteCallback( "Radio City Hall", myLocation )
 .then( function() {
   console.log( "geofence monitoring has been started" );
 })
 .catch( function( error ) {
   console.log( "error - " + result.message );
 });

Stop location monitoring


Stops client-side monitoring of the device's location for the geofence.

Non-blocking method:

Backendless.Geo.stopGeofenceMonitoring( geofenceName )
 .then( function() {
 })
 .catch( function( error ) {
 });
 

Blocking method:

Backendless.Geo.stopGeofenceMonitoringSync( geofenceName );
 

where:

geofenceName - name of the geofence for which device location monitoring will stop. If the value of the argument is null, the method stops location monitoring for all geofences.

Example:

Backendless.Geo.stopGeofenceMonitoring( "Home" );

 

Run the OnEnter Action


Requests the server to run the configured OnEnter action for a geofence either for all geopoints located within the geofence or for the specified geopoint.

Non-blocking method:

Backendless.Geo.runOnEnterAction( geofenceName, geoPoint )
 .then( function() {
 })
 .catch( function( error ) {
 });

Blocking method:

Backendless.Geo.runOnEnterActionSync( geofenceName, geoPoint );

where:

geofenceName - name of the geofence on which the OnEnter action will run.
geoPoint - a geopoint which will be used as the context for the action execution. Any substitutions which the action may be configured with will be resolved against the geopoint. If the value of the argument is null, the action is executed on all geopoints located within the geofence.

Example:

Backendless.Geo.runOnEnterAction( "Home" );

Run the OnStay Action


Requests the server to run the configured OnStay action for a geofence either for all geopoints located within the geofence or for the specified geopoint.

Non-blocking method:

Backendless.Geo.runOnStayAction( geofenceName, geoPoint )
 .then( function() {
 })
 .catch( function( error ) {
 });

Blocking method:

Backendless.Geo.runOnStayActionSync( geofenceName, geoPoint );

where:

geofenceName - name of a geofence on which the OnStay action will run.
geoPoint - a geopoint which will be used as the context for the action execution. Any substitutions which the action may be configured with will be resolved against the geopoint. If the value of the argument is null, the action is executed on all geopoints located within the geofence.

Example:

Backendless.Geo.runOnStayAction( "Home" );

Run the OnExit Action


Requests the server to run the configured OnExit action for a geofence either for all geopoints located within the geofence or for the specified geopoint.

Non-blocking method:

Backendless.Geo.runOnExitAction( geofenceName, geoPoint )
 .then( function() {
 })
 .catch( function( error ) {
 });

Blocking method:

Backendless.Geo.runOnExitActionSync( geofenceName, geoPoint );

where:

geofenceName - name of a geofence on which the OnExit action will run.
geoPoint - a geopoint which will be used as the context for the action execution. Any substitutions which the action may be configured with will be resolved against the geopoint. If the value of the argument is null, the action is executed on all geopoints located within the geofence.

Example:

Backendless.Geo.runOnExitAction( "Home" );

Retrieve Geopoints from a Geofence


Retrieves a collection of geopoints currently located within a geofence

Non-blocking method:

Backendless.Geo.getFencePoints( geofenceName, backendlessGeoQuery )
 .then( function() {
 })
 .catch( function( error ) {
 });

Blocking method:

Backendless.Geo.getFencePointsSync( geofenceName, backendlessGeoQuery );

where:

geofenceName - name of a geofence to retrieve the geopoints from.
backendlessGeoQuery - a Backendless.GeoQuery object controlling various aspects of geopoint retrieval such as paging, inclusion of metadata and/or SQL search query.

Example:

The example loads geopoints from a geofence called "Manhattan". The returned geopoints will have metadata properties and will match the SQL query:

var geoQuery = new Backendless.GeoQuery();
geoQuery.includeMeta = true;
geoQuery.condition = "businessType='restaurant' and rating > 3";

Backendless.Geo.getFencePoints( "Manhattan", geoQuery )
 .then( function( geoPointsArray ) {
   console.log( "found " + geoPointsArray.length + " geopoints" );
 });
 .catch( function( error ) {
   console.log( "error - " + result.message );
 });

Logging API

Overview

For Backendless, logging is the submission of mobile device client application log messages from a client to a server. The Logging API submits the messages to the Backendless server logging service. Messages can be any information the client developer chooses to include. For detailed information about Backendless Logging and all the server-side management aspects, see the Logging section of this documentation.

 

The Logging API uses the concept of "Logger" as a "gateway" for all the client-side log message submission functionality. An app may have multiple loggers, each identified by a unique name which may represent a UI screen name or a functional area of the app. To minimize client-server log traffic (which otherwise may be very verbose), the Backendless API introduces the concept of "log buffer." Log buffer is responsible for accumulating log messages and periodically delivering them to the server in a single batch. The buffer is configured by two parameters, number of messages the buffer can hold and time frequency, which is a time interval for periodic log message flushing. For details on log buffering see the Configuring a Log Buffer Policy section in this chapter

The Logging API supports the following functions:

Retrieving a Logger

This API retrieves a logger for the name or class. If one had been retrieved before (within the context of app launch) the system returns it, otherwise a new logger object is created and returned. All methods are available via Backendless.Logging accessor:

var logger = Backendless.Logging.getLogger(loggerName)

where:

loggerName        - any string value which identifies the logger.

 

Logging a message

This API logs a message and or error to the server. If the log buffer is configured, the message is stored in it for subsequent flush, otherwise, the log message is delivered to the server.All methods are available on the logger object retrieved using the method described above:

Backendless.Logging.getLogger(loggerName).debug(message);
Backendless.Logging.getLogger(loggerName).info(message);
Backendless.Logging.getLogger(loggerName).warn(message);
Backendless.Logging.getLogger(loggerName).warn(message, exception);
Backendless.Logging.getLogger(loggerName).error(message);
Backendless.Logging.getLogger(loggerName).error(message, exception);
Backendless.Logging.getLogger(loggerName).fatal(message);
Backendless.Logging.getLogger(loggerName).fatal(message, exception);
Backendless.Logging.getLogger(loggerName).trace(message);

where:

message - the message to log.
exception - an exception or error to log. Backendless logs a stack trace for the exception.
loggerName - any string value which identifies the logger.

Example:

Backendless.Logging.getLogger("com.mbaas.Logger").info("Starting application..");

Once the example runs, the Manage > Log Management screen in the Backendless Console will show the following:
log-file-sample.zoom50

Configuring log buffer policy

 

The log buffer policy controls the following aspects of log message submission from the client app to the server:

the maximum number of log messages stored in the buffer
the maximum time interval (in seconds) between message transmissions

 

The log exhibits the behavior defined below based on the configuration parameters established with the API:

flushes the log messages to the server when the maximum of messages have been collected
flushes the messages after the configured time interval elapses
delivers log messages immediately if the number of messages is set to 0 (zero)

 

To configure a log buffer policy, include the following in your application. The method is available via the Backendless.Logging accessor:

Backendless.Logging.setLogReportingPolicy( numOfMessages, timeFrequencySec )

where:

numOfMessages - sets the maximum limit for the number of messages.
timeFrequencySec - time frequency/interval in seconds defining how often log messages from the buffer should be flushed to the server. The value of zero indicates immediate delivery of messages to the server, bypassing the buffer.

 

Example:

The example below configures a log buffer policy for 100 messages and a 60 second flush time.

Backendless.Logging.setLogReportingPolicy(100, 60);

Caching API

Overview

Backendless Caching API provides a way to temporarily store data on the server in a highly efficient in-memory cache. The cache storage accepts key-value pairs where key is a string and value can be a primitive or complex data structure (arrays, complex types, maps/dictionaries, etc). The caching mechanism is cross-platform, that means Backendless automatically takes care of adapting data between heterogeneous client types. For instance, an Android client can put into cache an instance of Java object of type Person and an iOS (or any other) client can retrieve that object as an instance of the corresponding iOS (or other client type) class.

 

Restrictions

Maximum time duration a value can stay in cache is 2 hours (7200 seconds), but it's "life" can be extended with an API request.
Before an object is placed in cache, it is serialized as a byte array. The size of the serialized object cannot exceed 10240 bytes.
Maximum number of objects stored in cache varies between the pricing plans.

Putting data into cache

This API request places the object into Backendless cache and maps it to the specified key. If the timeToLive argument is not set, the object will expire from cache in 2 hours from the time when it is put in cache.

Non-blocking method:

Backendless.Cache.put(key, obj, timeToLive)
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

Backendless.Cache.putSync(key, obj, timeToLive);

where:

key - key assigned to the object to identify it in cache. The key is used to retrieve the object from cache or to check if the cache still contains the object.
obj - object to place into cache.
timeToLive - numeric value (in seconds) indicating how long the object must stay in cache before it is expires. When an object expires, Backendless automatically removes it from cache. The default value is 7200 seconds.

Example:

var order = // retrieving an object is out of scope for the example
  
var successCallback = function( response ) {
  console.log( "object has been put into cache" );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};
   
Backendless.Cache.put( "firstorder", order )
 .then( successCallback )
 .catch( failureCallback );

Retrieving data from cache

This API request retrieves an object from Backendless cache. If object is not present in cache, the method returns null for complex types or default value for primitive values.

Non-blocking method:

Backendless.Cache.get( key )
 .then( function( objectFromCache ) {
 })
 .catch( function( error ) {
 });

Blocking method:

var objectFromCache = Backendless.Cache.getSync( key );

where:

key - identifies the object to retrieve from cache.

Example:

// see the previous example to understand how the object got into cache

function Order() {
  var name;
}

var successCallback = function( response ) {
  var order = response;
  console.log( "object has been retrieved from cache. order name - " + order.name );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};
  
Backendless.Cache.get( "firstorder" )
 .then( successCallback )
 .catch( failureCallback );

Checking if key exists in cache

This API request checks if an object exists in cache. If object is present in cache, the method returns true, otherwise - false.

Non-blocking method:

Backendless.Cache.contains( key )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

var result = Backendless.Cache.containsSync( key );

where:

key - identifies the object to check in cache.

Example:

var successCallback = function( response ) {
  console.log( "object exists in cache - " + response );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};
  
Backendless.Cache.contains( "firstorder" )
 .then( successCallback )
 .catch( failureCallback );

Extending object's life in cache

There are two way to extend object's life in cache - relative timeframe and fixed timeframe. With the relative timeframe a period of time is added to the timestamp of the call to determine the new expiration time. The fixed timestamp approach sets the timestamp when the object must expire from cache.

Non-blocking method:

Backendless.Cache.expireIn( key, seconds )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Backendless.Cache.expireAt( key, timestamp )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

Backendless.Cache.expireInSync( key, seconds );
Backendless.Cache.expireAtSync( key, timestamp )

where:

key - identifies the object to extend the life of in cache.
seconds - number of seconds to extend the life of object in cache by. Must be a value between 1 and 7200 (2 hours).
timestamp - a timestamp in milliseconds when the object should expire and removed from cache. The difference between timestamp and the current time must be equal or less than 7200000 milliseconds (2 hours).

Example:

var successCallback = function( response ) {
  console.log( "[ASYNC] object's life has been extended/set" );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};
  
Backendless.Cache.expireIn( "firstorder", 1200 )
 .then( successCallback )
 .catch( failureCallback );
  
Backendless.Cache.expireAt( "firstorder", new Date( new Date().getTime() + 1200 ) )
 .then( successCallback )
 .catch( failureCallback );

Deleting object from cache

This method deleted an object from cache if it is present there.

Non-blocking method:

Backendless.Cache.remove( key )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

Backendless.Cache.removeSync( key );

where:

key - identifies the object to delete from cache.

Example:

var successCallback = function( response )  {
  console.log( "object has been removed from cache" );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};
  
Backendless.Cache.remove( "firstorder" )
 .then( successCallback )
 .catch( failureCallback );

Atomic Counters API

Overview

Backendless Counters API provides a centralized server-side facility for working with values that may be updated atomically. Every counter has a name assigned to it. The name is used in all operations to identify the counter. Since the counter value is managed centrally, multiple heterogeneous clients can access and modify the value.

 

All counter APIs are available via Backendless.Counters.[methodname] accessor. For example, the following code increments a counter and returns the current value:

Backendless.Counters.incrementAndGet( "my counter" );

Additionally, there is a shortcut approach (via obtaining a "counter manager") that facilitates operations on a specific counter:

var counterName = "MyCounter";

var successCallback = function( response ) {
  console.log( "counter value is - " + response );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};

// get counter manager
var counter = Backendless.Counters.of( counterName );

counter.incrementAndGet()
 .then( successCallback )
 .catch( failureCallback );

Increment by 1, return previous

Atomically increments by one the current value and returns the previous value of the counter. It is possible that multiple concurrent client requests may receive the same previous value. This occurs since only the incrementing part of the logic is atomic, the retrieval of the value before it is incremented is not.

Non-blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
Backendless.Counters.getAndIncrement( counterName )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );

counter.getAndIncrement()
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
var counterValue = Backendless.Counters.getAndIncrementSync( counterName );

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
var counterValue = counter.getAndIncrementSync();

where:

counterName - name of the counter to increment.

Example:

var successCallback = function( response ) {
  console.log( "counter value is - " + response );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};

// ************************************************
// Backendless.Counters approach
// ************************************************
Backendless.Counters.getAndIncrement( "my counter" )
 .then( successCallback )
 .catch( failureCallback );

// ************************************************
// Backendless.Counters.of() approach
// ************************************************
var myCounter = Backendless.Counters.of( "my counter" );
myCounter.getAndIncrement()
 .then( successCallback )
 .catch( failureCallback );

Increment by 1, return current

Atomically increments by one the current value and returns the updated (current) value of the counter. Multiple concurrent client requests are guaranteed to return unique updated value.

Non-blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
Backendless.Counters.incrementAndGet( counterName )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
counter.incrementAndGet()
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
var counterValue = Backendless.Counters.incrementAndGetSync( counterName );

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
var counterValue = counter.incrementAndGetSync();

where:

counterName - name of the counter to increment.

Example:

var successCallback = function( response ) {
  console.log( "counter value is - " + response );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};

// ************************************************
// Backendless.Counters approach
// ************************************************
Backendless.Counters.incrementAndGet( "my counter" )
 .then( successCallback )
 .catch( failureCallback );

// ************************************************
// Backendless.Counters.of() approach
// ************************************************
var myCounter = Backendless.Counters.of( "my counter" );
myCounter.incrementAndGet()
 .then( successCallback )
 .catch( failureCallback );

Decrement by 1, return previous

Atomically decrements by one the current value and returns the previous value of the counter. It is possible that multiple concurrent client requests may receive the same previous value. This occurs since only the decrementing part of the logic is atomic, the retrieval of the value before it is decremented is not.

Non-blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
Backendless.Counters.getAndDecrement( counterName )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
counter.getAndDecrement()
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
var counterValue = Backendless.Counters.getAndDecrementSync( counterName );

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
var counterValue = counter.getAndDecrementSync();

where:

counterName - name of the counter to decrement.

Example:

var successCallback = function( response ) {
  console.log( "counter value is - " + response );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};

// ************************************************
// Backendless.Counters approach
// ************************************************
Backendless.Counters.getAndDecrement( "my counter" )
 .then( successCallback )
 .catch( failureCallback );

// ************************************************
// Backendless.Counters.of() approach
// ************************************************
var myCounter = Backendless.Counters.of( "my counter" );

myCounter.getAndDecrement()
 .then( successCallback )
 .catch( failureCallback );

Decrement by 1, return current

Atomically decrements by one the current value and returns the updated (current) value of the counter. Multiple concurrent client requests are guaranteed to return unique updated value.

Non-blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
Backendless.Counters.decrementAndGet( counterName )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
counter.decrementAndGet()
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
var counterValue = Backendless.Counters.decrementAndGetSync( counterName );

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
var counterValue = counter.decrementAndGetSync();

where:

counterName - name of the counter to decrement.

Example:

var successCallback = function( response ) {
  console.log( "counter value is - " + response );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};

// ************************************************
// Backendless.Counters approach
// ************************************************
Backendless.Counters.decrementAndGet( "my counter" )
 .then( successCallback )
 .catch( failureCallback );

// ************************************************
// Backendless.Counters.of() approach
// ************************************************
var myCounter = Backendless.Counters.of( "my counter" );
myCounter.decrementAndGet()
 .then( successCallback )
 .catch( failureCallback );

Increment by N, return current

Atomically adds the given value to the current value and returns the updated (current) value of the counter. Multiple concurrent client requests are guaranteed to return updated value.

Non-blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
Backendless.Counters.addAndGet( counterName, value )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
counter.addAndGet( value )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
var counterValue = Backendless.Counters.addAndGetSync( counterName, value );

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
var counterValue = counter.addAndGetSync( value );

where:

counterName - name of the counter to increment.
value - number to add to the current counter value

Example:

var successCallback = function( response ) {
  console.log( "counter value is - " + response );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};

// ************************************************
// Backendless.Counters approach
// ************************************************
Backendless.Counters.addAndGet( "my counter", 1000 )
 .then( successCallback )
 .catch( failureCallback );

// ************************************************
// Backendless.Counters.of() approach
// ************************************************
var myCounter = Backendless.Counters.of( "my counter" );
myCounter.addAndGet( 1000 )
 .then( successCallback )
 .catch( failureCallback );

Increment by N, return previous

Atomically adds the given value to the current value and returns the previous value of the counter. It is possible that multiple concurrent client requests may receive the same previous value. This occurs since only the incrementing part of the logic is atomic, the retrieval of the value before it is incremented is not.

Non-blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
Backendless.Counters.getAndAdd( counterName, value )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
counter.getAndAdd( value )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
var counterValue = Backendless.Counters.getAndAddSync( counterName, value );

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
var counterValue = counter.getAndAddSync( value );

where:

counterName - name of the counter to increment.
value - number to add to the current counter value

Example:

var successCallback = function( response ) {
  console.log( "counter value is - " + response );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};

// ************************************************
// Backendless.Counters approach
// ************************************************
Backendless.Counters.getAndAdd( "my counter", 1000 )
 .then( successCallback )
 .catch( failureCallback );

// ************************************************
// Backendless.Counters.of() approach
// ************************************************
var myCounter = Backendless.Counters.of( "my counter" );

myCounter.getAndAdd( 1000 )
 .then( successCallback )
 .catch( failureCallback );

Conditional update

Atomically sets the value to the given updated value if the current value == the expected value.

Non-blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
Backendless.Counters.compareAndSet( counterName, expected, updated )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
counter.compareAndSet( expected, updated )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
var result = Backendless.Counters.compareAndSetSync( counterName, expected, updated );

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
var result = counter.compareAndSetSync( expected, updated );

where:

counterName - name of the counter to update.
expected - the expected value of the counter. If the current value equals the expected value, the counter is set to the "updated" value.
updated - the new value to assign to the counter if the current value equals the expected value.

Example:

var successCallback = function( response ) {
  console.log( "has the counter been updated? - " + response );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};

// ************************************************
// Backendless.Counters approach
// ************************************************
Backendless.Counters.compareAndSet( "my counter", 1000, 2000 )
 .then( successCallback )
 .catch( failureCallback );

// ************************************************
// Backendless.Counters.of() approach
// ************************************************
var myCounter = Backendless.Counters.of( "my counter" );

myCounter.compareAndSet( 1000, 2000 )
 .then( successCallback )
 .catch( failureCallback );

Get current counter value

Returns the current value of the counter.

Non-blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
Backendless.Counters.get( counterName )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
counter.get()
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
var result = Backendless.Counters.getSync( counterName );

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
var result = counter.getSync();

where:

counterName - name of the counter to get the current value of.

Example:

var successCallback = function( response ) {
  console.log( "counter value is - " + response );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};

// ************************************************
// Backendless.Counters approach
// ************************************************
Backendless.Counters.get( "my counter" )
 .then( successCallback )
 .catch( failureCallback );

// ************************************************
// Backendless.Counters.of() approach
// ************************************************
var myCounter = Backendless.Counters.of( "my counter" );
myCounter.get()
 .then( successCallback )
 .catch( failureCallback );

Reset counter

Resets the current counter value to zero.

Non-blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
Backendless.Counters.reset( counterName )
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
counter.reset()
 .then( function( result ) {
 })
 .catch( function( error ) {
 });

Blocking method:

// *******************************************
// Backendless.Counters approach
// *******************************************
Backendless.Counters.resetSync( counterName );

// *******************************************
// Backendless.Counters.of() approach
// *******************************************
var counter = Backendless.Counters.of( counterName );
counter.resetSync();

where:

counterName - name of the counter to reset.

Example:

var successCallback = function( response ) {
  console.log( "counter has been reset" );
};

var failureCallback = function( fault ) {
  console.log( "error - " + fault.message );
};

// ************************************************
// Backendless.Counters approach
// ************************************************
Backendless.Counters.reset( "my counter" )
 .then( successCallback )
 .catch( failureCallback );

// ************************************************
// Backendless.Counters.of() approach
// ************************************************
var myCounter = Backendless.Counters.of( "my counter" );

myCounter.reset()
 .then( successCallback )
 .catch( failureCallback );

App Management

Social Settings

Backendless provides support for logging in users with Facebook or Twitter credentials. Once logged in, Backendless creates a registered user in its internal user storage and links the Backendless user account with his external social account (see the "socialAccount" column in the Users table on the Data screen). To enable social login, a Backendless backend must be configured with API keys.

This configuration is available on the Manage > App Settings screen of the Backendless Console:

social-settings-facebook.zoom50

To obtain your Facebook App ID/API Key and App Secret:

2. Select an application or create one using the "My Apps" dropdown in the upper right corner.
3. Make sure the application is public and available to all users as indicated by the green dot next to the application name:
live-fb-app.zoom50
4. Locate the App ID and App Secret in the Settings section and copy the values into the corresponding fields in the Backendless Console (Manage > App Settings > Social Settings).
5. IMPORTANT: For the Backendless' "Easy Facebook Login" option make sure to make the following changes:
1. Click Settings on your Facebook application page.
2. Enter api.backendless.com into the App Domains field.