Realtime Code Hosting
Writing functions

  • Mar 21, 2015
  • Starting Guide

You might be wondering in which programming language are triggers and custom functions coded. If you’re thinking JavaScript you’ve guessed it! Your triggers and custom functions are executed in the context of a node.js process. We call them scripts.

You can get a glimpse of what a trigger looks like in the following example, where an item is only updated in the database if its timestamp property is not greater than the current server timestamp:

function beforeUpdate(res,req){
	// the item being updated
    var item = req.body.item;
    if(item.timestamp > new Date().getTime())
       res.send(400, "Timestamp is in the future");
    else
       res.send(200);
    }
}
							

The req parameter contains the request payload, including the item being updated. The res parameter will be used to build the trigger response. Because this is a beforeUpdate trigger, returning a 400 status code will cancel the database update being performed and the error will be returned with the response. More details about these parameters in the following sections of this document.

Custom functions can have additional parameters, like in the following example (user and password) that validates a user authentication:

function login(user, password, res, modules){ 

   // get a reference to the users table
   // assume the Users table has the username as primary key
   // and Password as an item attribute
 
   var userTableRef = modules.storageRef.table("Users");
   var userItemRef = userTableRef.item({
	    primary: user
    });

   // get the user item
   userItemRef.get(
	function success(itemSnapshot) {
		// Password matches? 
		if(itemSnapshot && itemSnapshot.val().Password === password)
		  // login was successful
		  // return 200 status code		
		  res.send(200);				
		else
		  // login failed
		  res.send(401, "Login failed");
		},
	function error(e){
		  // oops, something went wrong …
		  res.send(400, e);
	});
}
							

The req parameter

The req parameter is an object containing the REST request payload with the following attributes (for a custom function call with no request body):

{
  "headers": {
    "host": "codehosting.realtime.co",
    "content-length": "0",
    "accept": "application/json",
    "origin": "http://accounts.realtime.co",
    "user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) Chrome/41.0.2272.101",
    "content-type": "text/plain;charset=UTF-8",
    "referer": "http://accounts.realtime.co",
    "accept-encoding": "gzip, deflate",
    "accept-language": "en-GB,en;q=0.8,en-US;q=0.6",
    "x-forwarded-for": "90.153.245.194"
  },
  "body": ""
}								
							

When the function is invoked by a database trigger, the req object will have the following attributes (e.g. for a beforeDelete trigger on item with primary key "test" in table Users):

{
  "headers": {    
    "content-type": "application/json",
    "host": "codehosting.realtime.co",
    "transfer-encoding": "chunked",
    "x-forwarded-for": "10.17.55.162"
  },
  "body": {
    "key": {
      "primary": "test"
    },
    "table": "Users",
    "operation": "BeforeDeleteItem"
  }
}								
							

The operation attribute holds the trigger operation name and the body attribute holds the trigger operation REST API request body (without the appkey and private key).

You can find the complete Realtime Cloud Storage REST API reference here.

With the before triggers, the req.body.item will contain the item (or key of the item) that will be affected by the triggered database operation.

With the after triggers, the req.body.item will contain the item already affected by the triggered database operation, if the database operation was completed successfully.

The res parameter

The res parameter will be used to build and send the trigger response back to the caller. Your REST call will only return a response to the caller when the res.send method is called. Failing to do so will cause the REST call to fail with a timeout (after 10 seconds).

The res.send method has the following signature:

res.send(statusCode, [body] [,headers])

The headers object will contain the response headers. If for example you want to have a custom function that returns html code you need to change the default Content-Type from application/json to text/html using the following headers object:

var headers = {
    "Content-Type": "text/html" 
}

The body parameter will contain the response body. For the html use case we've mentioned before you could implement the following custom function returning a simple html page:

function htmlHello(res){
    
    var headers = {
        "Content-Type": "text/html" 
    };
    
    var body = "<html><body><h1>Hello World</h1></body></html>";
    
	res.send(200, body, headers);
}

A powerful use of the body response can be found in beforePut and beforeUpdate triggers where you apply some business logic to the req.body.item (the item being inserted or updated) and change it before it is commited to the database table. You achieve this by sending the altered item in the body.item parameter of the response body.

The following trigger function example will use the item's a and b attributes to create a new attribute sum holding a + b:

function add(res, req){
    var item_being_updated = req.body.item;
    
    item_being_updated.sum = item_being_updated.a + item_being_updated.b;
    
    var body = { 
        item: item_being_updated 
    };
    
	res.send(200, body);
}								
							

The modules parameter

The modules parameter contains several handy objects like the storageRef . The storageRef object will always be connected to your Realtime Cloud Storage database, allowing easy database operations from within the function scope, like retrieving and updating items. You can access the storageRef API here.

Also as you might have guessed you can easily access any external RESTful API from within your triggers and custom functions, like sending push notifications through Realtime Messaging, sending a SMS using Twilio or even an email using SendGrid.

To consume external REST API's you'll need to use the standard Node.js modules http and https available in the Code Hosting function modules parameter. The following code is an example of a Code Hosting function using the Twilio REST API to send a SMS message:

function sendSMS(text, from, to, res, modules){
  
    var encodedText = encodeURIComponent(text);            
    var sms = "From=" + from + "&To=" + to + "&Body=" + encodedText;
    
    var headers = {
      'Authorization': 'Basic [AUTHENTICATION]',
      'Content-Type': 'application/x-www-form-urlencoded',
      'Content-Length': sms.length
    };
    
    var options = {
      host: 'api.twilio.com',
      port: 443,
      path: '/2010-04-01/Accounts/[TWILIO ACCOUNT SID]/Messages.json',
      method: 'POST',
      headers: headers
    };
            
    var request = modules.https.request(options, function(response, body) {          
      res.send(response.statusCode, body);
    });
    
    request.on('error', function(e) {          
      res.send(400, JSON.stringify(e));
    });
    
    request.write(sms);
    request.end();
         
}								
							

Another module available in the modules parameter is the storageMule module. This module contains some handy functions to use other Realtime services like the Push Notifications. The methods available are:

  • modules.storageMule.Realtime.sendPushNotification
  • modules.storageMule.Realtime.sendMessage
  • modules.storageMule.Realtime.saveAuthentication
  • modules.storageMule.log

modules.storageMule.Realtime.sendPushNotification

Sends a custom mobile push notification using the Realtime Messaging Push Notitications service.

function sendPush(msg, payload, res, modules){

    modules.storageMule.Realtime.sendPushNotification({
            applicationKey: '[YOUR_APP_KEY]',
            privateKey: '[YOUR_PRIVATE_KEY]',        
            channel: '[YOUR_CHANNEL]',
            message: msg,
            payload: payload
    }, function(err){
            if(err){
                res.send(400, 'Error ' + err);
            } else {
               res.send(200, 'Notification has been sent');            
            }
    });

}									
							

modules.storageMule.Realtime.sendMessage

Sends a message using the Realtime Messaging service.

function sendMessage(channel, msg, res, modules){

    modules.storageMule.Realtime.sendMessage({
		applicationKey: '[YOUR_APP_KEY]',
        privateKey: '[YOUR_PRIVATE_KEY]',        
        channel: channel,
        message: msg
	});

}								
							

modules.storageMule.Realtime.saveAuthentication

Authenticates a token for the Realtime Messaging service.

function saveAuth(token, res, modules){

    modules.storageMule.Realtime.saveAuthentication({
        token: token,
        TTL: 1400,
        isPrivate: false,
        permissions:  { channel1: "wrp", channel2: "w" }
    }, function(err, result){
            if(err){
                res.send(400, 'Error ' + err);
            } else {
            	res.send(200, 'Save authentication: ' + result );            
            }
    });

}									
							

modules.storageMule.log

Sends a log message to the chDebug channel of your Realtime application key (note: regular Realtime Messaging billing will be applied).

function someFunction(res, modules){
    modules.storageMule.log('Log from Code Hosting mule: ', modules.storageMule.muleId);
}									
							

You can capture and filter these log messages using the LOGS page in your Realtime Cloud Storage console:

Executing a function in "background"

Sometimes it’s useful to send the response to the caller as soon as possible (to avoid the 10 seconds timeout) and execute the function logic in “background”, leveraging the node.js event loop async pattern. Obviously this technique is only possible when the function logic result is not supposed to be returned directly to the function caller, otherwise you’ll need to wait for the function logic to complete to send the computed response.

Consider this use case for a social app: when a user adds a post you want to send a custom push notification with the new post title to the posting user’s followers.

When a new post is added you could call a Code Hosting custom function (or configure a afterPut trigger in the posts table) to retrieve the user’s followers from the database, iterate through the list sending to each one a custom push notifications using the Realtime Messaging Push Notifications service and in the end return the result to the caller.

This would work for a small number of followers, but for large sets it could even cause a timeout. Not to mention that your user experience would not be great as the posting user would be waiting for a few seconds before the post operation finishes (in the case you were using a trigger).

In this case you could use the “fire-and-forget” pattern, a common pattern where you “fire” some asynchronous operation and “forget” about the results. If it succeeds it succeeds, if not, your app can live with it or recover from it (aka eventual consistency). In this case a fail would mean some followers would not get the push notification.

To use the “fire-and-forget” pattern in a Realtime Code Hosting function (or trigger) you simply call the res.send method right at the start of the function execution to send the response to the caller and continue with the remaining operations you want to “forget”.

The following function shows an example of how this could be achieved in a Code Hosting function:

function sendNotifications(userId, postTitle, res, modules) {
    
    // return the ok result immediately to the caller
	res.send(200);
	
	// continue with the remaining operations ...
	
	// set a table reference for the followers table
	var followersTableRef = modules.storageRef.table("Followers");
	
	// filter the items using the userId
	followersTableRef = followersTableRef.equals({ item: "userId", value: userId });
	
	// get the userId followers

	followersTableRef.getItems(
    	function success(itemSnapshot) {
    	    if(itemSnapshot && itemSnapshot.val()) {
    	        
    	        // for each follower ...
    	        var follower = itemSnapshot.val();
    	        
    	        // send a log to the remote console
    	        modules.storageMule.log("Sending push to ", follower.followerId);
    	        
    	        // send the push notification
    	        modules.storageMule.Realtime.sendPushNotification({
                        applicationKey: '[YOUR_APP_KEY]',
                        privateKey: '[YOUR_PRIVATE_KEY]',       
                        channel: follower.followerId,
                        message: postTitle
                    });            
            }
    	});
}

A final note: due to the single-threaded nature of a Code Hosting Mule (a Node.js process) you shouldn't block the event loop with heavy synchronous computational tasks but rather use asynchronous functions. Checkout this StackOverflow thread about the Node.js event loop.

Listing and editing scripts

The Realtime Console allows you to easily list your scripts and edit them on the fly. The next image shows a snapshot of the Realtime Scripts Console:

Clicking the edit button will allow you to edit your script on-line. The following image shows the Realtime Script Editor with the login function mentioned in the previous sections:

At some point we'll need to talk about servers and infrastructure. In the next section we'll tell you all about how you can scale your Code Hosting infrastructure.

Proceed to scaling

If you find this interesting please share: