Developer Docs

Routing

What is a route?

Route enables you to design and implement RESTful services that run on HTTP. It helps you determine how your application responds to a client request at a particular endpoint. It (HTTP web service) contains a set of functions which help you access and process your application’s data and return the processed response to the client. Every HTTP request contains all the information required to run it, which means knowledge of previous state need not be remembered by either the client or the server.

Within a route, you can access the following services within OBTO platform:

  • Run database queries
  • Access server scripts
  • Run data aggregations queries
  • Communicate information to other services

To learn more on how to implement a route, continue reading below.

Components of a route

Name: A friendly name used to identify the route within the platform.

Path: Specific URI name for this endpoint.

Active: Activate or deactivate route (true or false).

Secured: Boolean (true or false). To distinguish between public and private routes. If secured, then X-ACCESS-TOKEN must be passed in every request.

Method: HTTP request method (GET or POST).

Description: Add comments to what this route does. Non-mandatory field.

Script: The route content, contains the application logic.

Router: The routing engine gives you the options to pick from 3 different types of routers.

  • API: Best suited to create RESTful interfaces for you application. Can be used with third party tools, mobile and web client sides, for rapid prototyping and much more. API token is required to access all API routes.
  • MS: Optimized to build microservices, to handle data intensive tasks. Can be secured or unsecured.
  • SITE: Optimized for serving any static content for your website pages. Example - images, media, HTML.

Full path to a route

Constructing your route path is very simple. Follow the notation below to find the full path.

https://{domainName}.obto.co/{routerType}/{routePath}

domainName: the name of your instance given at the time of registration.

routerType: API, MS or SITE

routePath: path specified in the route at the time of its creation.

Example: https://test.obto.co/ms/staticcontent.bto

Creating your first route

Let’s create a simple route to return “Hello World” string.

Script:

module.exports.testStaticContent = () => {
    return (req, res) => {
        let responseHTML = "Hello World!";
        res.status(200).send(responseHTML).end();
    }
}

Result:

Hello World!

Query Parameters

Accessing query parameters is very easy and follows the standard approach as shown below.

Request URL:

https://test.obto.co/api/userinfo.bto?name=billy&age=10

Code:

req.query: { "name": "billy", "age": "10" }
let name = req.query.name;
let age = req.query.age;

Path Parameters

To create path parameters in a route, specify the path as below:

Path:

/userinfo.bto/:name/:age

Request URL:

https://test.obto.co/api/userinfo.bto/billy/10

Code:

req.params: { "name": "billy", "age": "10" }
let name = req.params.name;
let age = req.params.age;

Handling Exceptions

Standard javascript exceptions can be thrown while developing routes. Or you can choose to print error logs. Both exceptions and log statements can be viewed under Error Logs. To learn about the syntax of error logs, please read here.

Best Practices

We have curated a list of best practices to follow while create your routes which will help you write maintainable and scalable code.

  • Always check for undefined values. Specially when reading data from request parameters or post body.
  • Code for async control flow. Javascript is asynchronous by nature so you should always keep that in mind while writing your code.

Database

OBTO platform leverages the strengths and capabilities of MongoDB and continues to build upon it. If you have previous experience working in a NoSQL environment then you’ll feel right at home. For those making the switch from a SQL stack will find it easy to transition as well.

You can perform the following two types of actions:

  • Run queries to create, find, update, delete records.
  • Run data aggregations.

Let’s explore the two cases below.

Create document

ob.db.create(collectionName, query, createOptions, callbackFunction);

collectionName: (String) collection from which data is fetched.

query: (Object) object to create.

createOptions: (Object) specify any special options for create.

callbackFunction: (function) Takes two parameters, an error and document of type object. null if no error occurs and null if no document found.

Code:

let objToCreate = {
    "domain": “demo",
    "name": “Tony Stark”,
    "affiliation": "avenger",
    "species: "human"
};

let createOptions = {};

let callback = (err, document) => {
    if(err){
        throw new Error(err);
    }
    else{
        print(document);
    }
};

ob.db.create("user", objToCreate, createOptions, callback);

Explanation:

The code above creates a objToCreate document in the ”user” collection.  In the callback function, it throws the error if it occurs, else it prints the document.

Find one document

ob.db.findOne(collectionName, query, findOptions, callbackFunction);

collectionName: (String) collection from which data is fetched.

query: (Object) query parameters.

findOptions: (Object) specify fields to project, sort order. Use empty object for no options.

callbackFunction: (function) Takes two parameters, an error and document of type object. null if no error occurs and null if no document found.

Code:

let query = {
    "domain": “demo",
    "name": “billy”
};

let findOptions = {
    "fields": [“name”, “age”],
    "sort": [\
         ["name", "asc"]\
     ]
};

let callback = (err, document) => {
    if(err){
        throw new Error(err);
    }
    else{
        print(document);
    }
};

ob.db.findOne("user", query, findOptions, callback);

Explanation:

The code above finds one document in the ”user” collection. Only searchs for documents with ”demo” domain and “billy” name. Sort the data in ascending order on ”name” field and only projects “name” & “age” fields in the resulting document.

Find multiple documents

ob.db.findWithOptions(collectionName, query, findOptions, callbackFunction)

collectionName: (String) collection from which data is fetched.

query: (Object) query parameters.

findOptions: (Object) specify fields to project, sort order. Use empty object for no options.

callbackFunction: (function) Takes two parameters, an error and document of type array. null if no error occurs and empty array if no documents found.

Code:

let query = {
    "domain": “demo",
    "name": “billy”
};

let findOptions = {
    "fields": [“name”, “age”],
    "sort": [\
         ["name", "asc"]\
     ]
};

let callback = (err, documents) => {
    if(err){
        throw new Error(err);
    }
    else{
        print(documents);
    }
};

ob.db.findWithOptions("user", query, findOptions, callback);

Explanation:

The code above finds all documents in the “user” collection with domain “demo” and name “billy”. It sorts the data in ascending order on “name” field and only projects “name” & “age” fields in the resulting documents.

Stream documents

ob.db.findStream(collectionName, query, findOptions, endOfDocument, onComplete);

collectionName: (String) collection from which data is fetched.

query: (Object) query parameters.

findOptions: (Object) specify fields to project, sort order. Use empty object for no options.

endOfDocument: (function) Returns a document found in the stream.

onComplete: (function) Called after every document is stream. Take in an error parameter. It is null if error doesn’t occur.

Code:

let users = [];
let query = {
  "created_on": {
    "$gte": new Date(start),
    "$lte": new Date(end)
  },
  "domain": "demo"
};

let options = {
  fields: ['name'],
  propogate: false,
  sync: false,
  user: "admin",
  domain: "demo"
};

let eod = function(doc) {
  users.push(new ob.objectId(doc._id));
};

let ocom = function(err) {
  print("Stream ended.");
};

ob.db.findStream("users", query, options, eod, ocom);

Explanation:

The code above creates a new stream to find documents in “user” collection based on given query parameters and options. options object takes in the following keys:

fields: fields to project in the resulting document.

propogate: if true, fires server policies after event has been fired.

sync: if true, syncs all objects in memory.

user: user name of user taking the action.

domain: name of instance.

In the end of every document streamed, it creates a new id from document’s _id and pushed it to users array. In stream through all documents, it goes to on complete function to print “Stream ended.” statement.

Find distinct document

ob.db.findDistinct(collectionName, distinctFieldName, findQuery, filterOptions, callback);

collectionName: (String) collection from which data is fetched.

distinctFieldName: (String) field name for which distinct documents are to be found.

findQuery: (Object) specific parameters to search for.

filterOptions: (Object) specify fields to project or sort order.

callback: (function) Takes two object parameters, an error and array of found documents.

Code:

let distinctFieldName = “departure_time”;

let findQuery = {
  “fligh_from”: “SFO”,
  “status: “departure”,
  “domain”: “demo”
};

let filterOptions = {
  sort: [\
    ["departure_time", "asc"]\
  ]
};

let callback = (err, documents) => {
  if(documents.length === 0) {
    print(“No departing flights from SFO at the same time.”);
  }
  else {
    print(“Flights departing from SFO at the same time are: ”+documents.length);
  }
};

ob.db.findDistinct("departures", distinctFieldName, findQuery, filterOptions, callback);

Explanation

The code above searches for distinct documents in “departures” collection. It finds departing flights from SFO airport that will depart at the same time. It finds documents with distinct departing times and will sort the resulting documents in ascending order by “departure_time”. In the callback, it will check if distinct documents exist and will print the result accordingly.

Update one document

ob.db.update(collectionName, searchQuery, objToUpdate, options, updateOptions, callback)

collectionName: (String) collection from which data is fetched.

searchQuery: (Object) search parameters to find the record.

objToUpdate: (Object) data fields which needs to be updated.

options: (Object) contains two fields, upsert (whether to create new record if it doesn’t exist or not) and multi (whether to update multiple records or not).

updateOptions: (Object) contains details about the user updating the record and instructions about syncing objects and triggering server policies on update.

callback: (function) Takes two object parameters, an error and result of update query.

Code:

let searchQuery = {
  "_id": new ob.objectId("123456789012")
};

let objToUpdate = {
  "$set": {
    "updated_on": new Date(ob.moment())
  }
};

let options = {
  "upsert": false, //whether to create new record if it doesn’t exist or not
  "multi": false //whether to update multiple records or not
};

let updateOptions = {
  "user": {
    "userid": "admin", // userid of
    "user_name": "admin"
  },
 "domain": "demo",
 "sync": true,  //sync objects in memory
 "propogate": true //whether to trigger server policies on update or not
};

let callback = (err, result) => {
  print(result)
};

ob.db.update("user", searchQuery, objToUpdate, options, updateOptions, callback);

Explanation

The code above searches for a record in “user” collection with the given object id. It then updates its “updated_on” field with current time. It does so with the given update options. Result is printed in the callback function.

Update multiple documents

Use the same query from update one document, but specify multi true in options. This will update multiple records which satisfy the search query.

Code:

let options = {
  "upsert": false, //whether to create new record if it doesn’t exist or not
  "multi": true //whether to update multiple records or not
};

Find and modify document

 ob.db.findAndModify(collectionName, findQuery, sortOrder, objToUpdate, options, callback);

collectionName: (String) collection from which data is fetched.

findQuery: (Object) query parameters to find the record.

objToUpdate: (Object) data which needs to be updated.

options: (Object) contains options for update. “new” field indicates whether or not to create new record if nothing is found.

callback: (function) Takes two object parameters, an error and result of update query.

Code:

let findQuery = {
  "_id": new ob.objectId("123456789012")
};

let sortOrder = [];

let objToUpdate = {
  "$set": {
    "status": "sent",
    "updated_on": new Date(),
    "updated_by": "admin"
  }
};

let options = {
  "new": true //whether to create new record if nothing found
};

let callback = (err, result) => {
  if (!err) {
    print(result);
  }
};

ob.db.findAndModify("user", findQuery, sortOrder, objToUpdate, options, callback);

Explanation

The code above first finds a record in the “user” collection that matches the findQuery. If it find a record, it updates that record with data in objectToUpdate. If it doesn’t find any record, it creates a new record with objectToUpdate data. options object specifies update options for this query, whether to create new record if nothing found or not. The callback function then prints the update result.

Delete document

let DB = ob.db.getConnection();
DB.collection(collectionName).remove(searchQuery, options, callback);

DB: (Object) establish a connection to the database.

searchQuery: (Object) query parameters to find the record.

options: (Object) delete options.

callback: (function) Takes two object parameters, an error and result of delete query.

Code:

let DB = ob.db.getConnection();

DB.collection("message").remove({
  "domain": "demo",
  "status": "delivered"
}, {}, function(err, result) {
  if (err) {
    throw new Error(err)
  }
  else {
    print(result)
  }
});

Explanation

The code above first deletes ALL records in the "message" collection that has domain "demo" and status "delivered". If an error occurs, it throws the error as a new error object, otherwise it prints the result.

Data Aggregation

OBTO platform allows the execution of aggregation operations provided by MongoDB. _“_Aggregation operations process data records and return computed results”. To read more on the different types of available operations, please visit MongoDB’s website on Aggregations.

Following examples illustrates how an aggregation structure will look like. You will first need to create a Data Source under Analytics tab. It takes in the following parameters:

Name: unique name for this aggregation ( must be single word)

Label: Friendly label (spaces allowed)

Collection: collection name on which this aggregation is to run

Script: the aggregation code. It has the following object structure:

  • iproject: contains all fields that are required for aggregation
  • match: query to match records by.
  • group: defines how to group records.
  • project: defines what fields to project in the final result.
  • sort: what field to sort final data on.

The script below is an example which fetches the attendance of all students in a class and groups them by the total counts for each student in class. Note: all operations used in the code below can be found on MongoDB website.

Code:

**name:**attendancebyclass

{
  "iproject": {
    "present": {
      "$cond": {
        "if": {
          "$eq": ["$present", true]
        },
        "then": 1,
        "else": 0
      }
    },
    "domain": 1,
    "class_name": 1,
    "student_name": 1,
  },
  "match": {
    "class_name": "{{className}}"
  },
  "group": {
    "_id": {
      "name": "$student_name._id"
    },
    "present": {
      "$sum": "$present"
    },
    "total": {
      "$sum": 1
    }
  },
  "project": {
    "present": "$present",
    "total": "$total",
    "name": "$_id.name",
    "_id": 0
  },
  "sort": {
    "name": 1
  }
}

Once your aggregation query is ready, you can test it using the following piece of code. Try running it in the scripting console!

let commons = {
  "domain": "demo",
  "time_zone": "America/New_York",
  "filter": {
    "className": "5 ABC"
   }
};

let callback = function(err, data) {
  if(err)
    throw new Error(err)
  else
    print(data)
};

ob.db.findOne('pltf_data_source', {
  "name": "attendancebyclass"
}, {
  name: 1,
  script: 1,
  label: 1,
  _id: 1,
  color: 1,
  collection: 1
}, function(err, doc) {
  if (!err && doc !== null)
    ob.agg().get(doc.collection, doc, commons, callback);
});

Explanation

commons object contains the filter query which is accessed by the aggregation script. It has a “className” field which is used to match the class name in aggregation script. The callback function then prints the resulting data after execution.

Dashboard Page

As a developer you can build a web application on the OBTO platform using javascript frameworks such as Angular.js or React.js. Every such platform application will have the following three components, which we’ll explore in details.

  • Dashboard Page : represents a platform application and defines its structure.
  • UI Template : front end javascript for the application.
  • Client Policy : back end javascript for the application.

The name of a dashboard page is the unique name used to access a platform application. You can view a complete list of dashboard pages in the Dashboard Pages under Configure nav tab. Or you can access it using the following URL

https://{yourDomainName}.obto.co/pltf_dashboard_page/list

A dashboard page will have the following fields:

Domain: (String) instance on which this application will be accessible.

Name: (String) this in a unique name (without any special characters) that will be used to form the full path to the application.

Global landing: (Boolean) indicating whether this application will be the landing page for all user upon login

Roles: (String) what users will have access to this application.

Schema: (JSON) A flexible schema for this application. Linking what UI template(s) to load for this application. Following is the structure to use:

{
  "schema": [{\
    "widgets": [{\
      "name": "",\
      "icon": "fa fa-list-alt",\
      "chart": {\
        "id": "bar1-chart",\
        "name": nameOfUITemplate,\
        "isHtmlTemplate": true\
      }\
    }],\
    "size": "1"\
  }]
}

Note: nameOfUITemplate is the name of UI Template to be used for this application.

When you create a new UI Template, its subsequent dashboard page is automatically created.

Accessing Your Application

The full path to any platform application can be formed using the following naming convention.

https://{yourDomainName}.obto.co/page?pagename={dashboardPageName}

UI Template

A UI Template is nothing more than the front end of an application (HTML, CSS and JS), with the additional ability to control who has access to view it. You would create a UI Template as you would normally code for a regular web application front end. It has the following fields:

Name: unique name. This is used as a reference name in a dashboard page and a client policy.

Version: version number of this template

Category: used to control who has access to it

  • System: accessible only with the OBTO platform for platform applications
  • Email: same as system, but supports an email template format
  • Public: accessible on the public internet.

HTML: front end code for the application.

Notes: OBTO platform comes installed with jQuery and Bootstrap 4 so you don't have to include them separately.

Following is an example UI Template:

<h1>Hello World!</h1>
<p>My first UI Template.</p>

<script>
$( document ).ready(function() {
    console.log( "ready!" );
});
<script>

Policies

Client Policy

A client policy is synonymous to the controller logic of a platform application. It is where the backend javascript code goes for any platform application and has the following fields:

Name: Unique name for this record.

Type: To control when the script executes

  • Execute onLoad: the default execution type (if you are not sure about it, then use this)
  • Execute onChange:

Collection: This is a reference to corresponding UI Template for this application. The collection name and the name of its UI Template must be an exact match.

Script: Javascript controller logic. All code must be encapsulated return function. By default access to regular angular services is available in the obto parameter.

return function(obto) {
  let $scope = obto.scope;
  const $rootScope = obto.root;
  let $http = obto.http;
  let timeout = obto.timeout;
  let location = obto.location;

let playerName = “Tony Stark”;
};

You can access variables in the UI Template as you would in a regular Angular application like below:

<div>
    <span>{{playerName}}</span>
</div>

Server Policy

Server policies are used to execute events (or perform checks) whenever any type of query is made to a collection. There are numerous powerful benefits of using server policies while designing your application! It can be used to ensure correct data format during a create query, limit data access as per user role during a find query, send mobile notifications in certain scenarios, and much more. Policies can be placed during pre or post execution of a create, update, delete or a find query. Following are the required fields:

Name: (String) label name for this record.

Execution Time: period when this policy is triggered, Pre Query or Post Query.

Collection: name of the collection on which this policy is added.

Action: query, update, create, delete.

Order: execution order of this policy, default to 10.

Active: whether policy is enabled or not, true/ false.

Domain: default to “global”.

Script: Code to execute.

  • done(_this) is called to in order complete the execution. Unsuccessful attempt at calling this function will throw an error in the execution chain.

  • _this object is used to access the record in context of a create, update or delete query.

  • Ex - _this.username will give the username value for the record.

  • user object can be used to reference to the user performing the query on this collection.

    • Ex - user.roles will give all the roles set for requesting user.

The following example showcases the use of a pre find query server policy on the “message” collection to control what records a user see when they view the datatable. For all non-admin users, the results of the find query are filtered according to their username. If user has an admin role, then no filter is applied and all records are shown.

try {
  if (user.roles.indexOf("admin") === -1) {
    done(_this);
  }
  else {
    _this.sent_to = user.user_name;
    done(_this);
  }
}
catch (error) {
  throw new Error(error);
  done(_this);
}

Security Policy

Security policies are used to control user access on collections. It specifies what roles have access to a collection. It has the following required fields:

Domain: your instance name.

Access Grants: comma separated roles that have access this collection.

Collection: name of the collection for which this security policy is created.

Note: Security policy is required for every collection, else the collection page will be an empty grid.

Server Script

Found under Scripts navigation tab, Server scripts allow you to define javascript classes which can then be exported as modules to be used in other parts of the platform. Any piece of code that is reused across different places in the application (routes, server policies, other server scripts, scheduled jobs) should be modularized using server scripts. Refer to the example below for how to structure your server policy.

class helloWorld {

/*
   * Constructor for this class
   * name: user name of the user
   */
  constructor(name) {
    this.username = name;
  }

/*
   * Function to print user name
   */
  this.printUserName() {
    ob.log("Printing user - " + this.username)
  }

}

module.exports.helloWorld = helloWorld; //Don't forget to export the class!

To instantiate an object of your server script class (whether it's in routes, other server scripts, schedule jobs, server policies, or scripting console), use the new xe.className format as below.

let obj = new xe.helloWorld("tony.stark");
obj.printUserName(); //Prints "Printing user - tony.stark" in the logs

Platform Properties

Found under Configure, platform properties are best used to create constant global variables. They can be accessed from any part of the platform, such as a route, scripting console, schedule job, server script or server policy. The following are the fields needed to create new property:

Name: unique name for the property

Type: String, Integer, Color. Type of value stored in the value.

Active: true/false

Value: data value for the property

Description: friendly name describing it.

Domain: your instance name.

Following is the correct way to access a property:

/*
 * propertyName - (String) name of the property
 * domain - (String) instance name of the property
 */
ob.getProperty(propertyName, domain);

Scripting Console

Scripting Console is the easiest way to test your code during development. You have full access to all platform modules as you normally would. Just need to copy your code and run it in the console.

We strongly suggest to develop your application in small parts and make sure data is flowing across as expected. These parts can then be tested in the scripting console to ensure expected functionality.

Note: You can print a log statement from the scripting console as well. Any error thrown in the scripting console will be shown under Error Logs.

Scripting Console can be found under the Scripts navigation tab.

Debugging

This is the most critical part of developing an application. OBTO offers a comprehensive error logging feature. Error Logs can be found under Logs navigation tab. Following is the correct syntax of printing log statements from anywhere in the application.

ob.log(statement, category, domain);

statement: (String) log statement to be printed.

category: (String) optional value to log type, used to distinguish between logs in Error Logs. Example - “error”, “log”.

Domain: your instame name.

Code:

ob.log("This is an error log - " + JSON.stringify(error), "error", "demo");

Collection

Create Collection

It is very easy to create a new collection in OBTO platform. Click the Create Collection link under Configure, or you can navigate to the following url:

https://{yourDomainName}.obto.co/createcollection.bto

You can view a full list of all your current collection on your instance. To make a new one, click on the green Create button in the top left corner, enter your unique collection name in the pop-up box and click the Create button.

Then create a new server policy for this collection with the appropriate roles to manage who can access this collection. Refer to Security Policy for more details.

Finally you collection can be viewed using the following url:

https://{yourDomainName}.obto.co/{collectionName}/list

Manage Access Roles

To restrict access to a collection, you can either specify the roles which will have read and edit access in it’s Security Policy. Or you can use Server Policies to manage what kind of CRUD operation can user perform on the collection.

Manage Fields

OBTO platform gives you the full flexibility to design a collection as per your business needs. Every time you make a new collection, it’ll have the following fields as default:

created_by, updated_by, created_on, updated_on, domain, app

To edit fields, click the Fields link under Configure navigation tab and filter the records by the collection you want to edit.

Add New Field

To add new field, you can wither create a new field here for the collection, or Copy Insert an existing record and edit its values.

Disable Edit Field

To disable edit access on a field, enable r eadonly totrue

Delete Field

To delete a field in the collection, delete the field record form Fields(pltf_field) collection.

Download Data (JSON or XLS)

To download table data, first select the records you want to download. Then click on the setting gear icon in the top left corner and select your appropriate download format (JSON or Excel).

Upload Data (JSON)

To upload data (JSON), click the setting gear icon in top left corner and upload data. Using the file uploaded, select the file(s) you want to upload.

App Manage

Coming soon.

Content Hosting

Coming soon.

Transform Maps

Coming soon.

Cookbooks

Coming soon.

Disqus Comments

We were unable to load Disqus. If you are a moderator please see our troubleshooting guide.

G

Start the discussion…

Comment

Log in with
or sign up with Disqus or pick a name

Disqus is a discussion network

  • Don't be a jerk or do anything illegal. Everything is easier that way.

Read full terms and conditions

This comment platform is hosted by Disqus, Inc. I authorize Disqus and its affiliates to:

  • Use, sell, and share my information to enable me to use its comment services and for marketing purposes, including cross-context behavioral advertising, as described in our Terms of Service and Privacy Policy, including supplementing that information with other data about me, such as my browsing and location data.
  • Contact me or enable others to contact me by email with offers for goods or services
  • Process any sensitive personal information that I submit in a comment. See our Privacy Policy for more information

Acknowledge I am 18 or older

I'd rather post as a guest

Favoriting means this is a discussion worth sharing. It gets shared to your followers' Disqus feeds, and gives the creator kudos!

Find More Discussions

Share

  • Tweet this discussion

    • Share this discussion on Facebook
    • Share this discussion via email
    • Copy link to discussion
  • Best

Be the first to comment.

Load more comments

live.rezync.com

live.rezync.com is blocked

This page has been blocked by an extension

  • Try disabling your extensions.

ERR_BLOCKED_BY_CLIENT

Reload

This page has been blocked by an extension

pippio.com

pippio.com is blocked

This page has been blocked by an extension

  • Try disabling your extensions.

ERR_BLOCKED_BY_CLIENT

Reload

This page has been blocked by an extension