23 Implementing Custom APIs

As a service developer, you use the custom code service to implement the custom APIs that your team creates for its mobile apps.

What Can I Do with Custom Code?

Using JavaScript, Node, and the custom code SDK, you can implement the APIs that have been designed in the API Designer (or by means of a RAML document). Say, for example, that your mobile app developer has designed the following API, which has one resource (/incidents), and two endpoints (GET /incidents, and POST /incidents).

#%RAML 0.8
title: IncidentReport
version: 2.0
baseUri: /mobile/custom/incidentreport
...
/incidents:
  displayName: Incident Reports
  get:
    description: |
      Retrieves all incident reports.
...
  post:
    description: |
      Creates a new incident report.       

You, as the service developer, implement all the endpoints in the API. That is, you write code to return incident reports for the first endpoint and to store incident reports for the second endpoint.

Your custom API implementation can call platform APIs (such as Storage and Notifications), other custom APIs, and external REST and SOAP web services. And it can access the external web services either directly or through connectors.

Note:

To use your implementation (custom code) in a mobile backend, you must first define the custom API as described in Custom API Design. The implementation is then accessed by your apps through calls to the API's endpoints.

How Does Custom Code Work?

Using the custom code service, you write JavaScript code to implement a custom API. The coding model is based on Node, which is a JavaScript framework that enables you to write server-side code and that provides a runtime environment for it. For each API endpoint, which is the resource (URI) plus the HTTP method such as GET or POST, you need a route definition that specifies how to respond to a client request to that endpoint. In other words, for each URI and HTTP method combination in your API, you need to add a JavaScript method to your custom code that handles the request. Route definitions follow the coding style promoted by Express, which is a module that runs in Node. We’ll show you how to write these methods.

After you’ve written your custom code, you package it as a Node module, and then upload it.

For more information about route definitions, see Key JavaScript Constructs in Custom Code. For information about the Express coding style, see http://expressjs.com/starter/basic-routing.html. For information about Node, see www.nodejs.org. If you’re interested in how the custom code service handles custom API requests and responses, then see What Happens When a Custom API Is Called?

Note:

Note: The purpose of the examples in this chapter is to illustrate how to interface with the custom code service. The examples are not intended to teach best practices for writing Node.js REST API implementations.

What's the Foundation for the Custom Code Service?

The custom code service is backed by the following JavaScript libraries, which provide key functionality. You use these libraries to implement your custom API.

  • Node

    Node provides the backbone for the custom code service. When you implement a custom API, you create a Node.js module.

    Behind the scenes, a router module takes care of creating an HTTP server for a Node instance and routing the HTTP calls that come from the service to the custom API’s implementation that runs inside the instance. You don’t need to write code for this.

  • Request

    Request is framework for Node that simplifies the making of HTTP calls. the service wraps Request calls with additional code that’s necessary for the custom code service.

  • Express

    Express is a lightweight web application framework for Node. The custom code service uses it to expose API endpoints. To implement your custom API, you write route definitions similar to how you would use Express to write routes for a web app.

  • Bluebird

    The custom code service uses the Bluebird promises library to implement the promises that the custom code SDK methods return.

  • Custom code SDK

    This SDK is available to custom API implementations and is what you use to call platform APIs, connectors, and other custom APIs, as described in Calling APIs from Custom Code. You also use it when you want your REST responses to be compatible with the Synchronization library, as described in Implementing Synchronization-Compatible APIs.

Note:

The Express, Request, and Bluebird modules are used by the custom code service but, with the exception of the Bluebird then operation, aren’t available for you to use explicitly in your implementation unless you package them with your custom API implementation. You can add additional JavaScript libraries to your implementation package, as long as they are compatible with the Node, Request, Express, and Bluebird versions and they don't require the installation of a binary (executable) on the server. See Packaging Additional Libraries with Your Implementation.

As shown in the following table, the default library versions depend on whether your environment was provisioned from the current release or upgraded from an earlier release.

JavaScript Library Environment Provisioned from Current Release Environment Upgraded from Prior Release
Node 6.10.0 0.10.25
Request 2.74.0 2.34.0
Express 4.14.0 3.5.1
Bluebird 3.4.6 2.9.30

If a custom API implementation isn't compatible with the default library versions for your environment, use one of the following processes to change the versions for that implementation:

  • Add a node property to the configuration section in the custom API implementation's package.json file as described in Declaring the Node Version. You can set it to either 0.10 or 6.10. See the preceding table for the library versions that are associated with each of these Node versions. The Node version in the package.json file overrides the CCC_DefaultNode environment policy for that custom API implementation.

  • Ask your mobile cloud administrator to change the node version that is specified by the appropriate CCC_DefaultNode environment policy. The choices are 6.10 and 0.10. See the preceding table for the library versions that are associated with each of these Node versions. You can set this policy at different scopes, such as environment scope, mobile backend scope, and API scope. Whenever you change a CCC_DefaultNode environment policy, any custom API implementation that uses that default configuration will change to the new version no later than its second REST request after the version change.

Note:

The default maximum body size for the Node 0.10 configuration is 1 MB. The default maximum body size for the Node 6.10 configuration is 100 KB. To learn how to increase the maximum body size for Node 6.10, see Custom Code Problem parsing JSON: Error: request entity too large.

Video: Node.js Technology Primer

If you don’t have experience with Node.js or you’d simply like to better understand how it works with the custom code service, take a look at this video:

Setting Up Tooling for Custom Code

The custom code service is based on Node. You don’t need to install Node on your system to create custom API implementations, but you’ll need the tooling that it provides, such as the Node package manager (npm). Having Node on your system also makes it easier for you to write the code.

The nodejs.org website provides installers that contain the library and some command-line tools, such as npm. You may wish to also install an integrated development environment (IDE) with Node support for features such as syntax highlighting and code completion. One free option is to install Eclipse (eclipse.org) and then add the Nodeclipse plug-in (http://www.nodeclipse.org/).

Steps to Implement a Custom API

The main steps for defining and implementing a custom API are the following:

  1. Define a custom API as described in Custom APIs.

  2. Download a JavaScript scaffold for the API. This scaffold contains stub implementations for your endpoints.

  3. Within the scaffold, fill in the appropriate JavaScript code for each function that corresponds with a given REST endpoint.

  4. Package the finished JavaScript module.

  5. Upload the module to the API Designer.

Downloading a JavaScript Scaffold for a Custom API

After you create your custom API, you can download a scaffold that is based on your API's RAML document, and then use the scaffold as a quick start for implementing your custom API.

Note:

Instead of downloading the scaffold, you can have it pushed directly to a Git repository. See Managing Custom Code in Git.

The scaffold comes in the form of a Node module, the key components of which are the main JavaScript file that contains stub methods for each endpoint (resource plus HTTP method), and a package.json file, which serves as the manifest for the module.

To download the scaffold:

  1. Click This is an image of the sidebar menu. to open the side menu, and then click APIs. Next, open the API that you want to implement.
  2. In the left navigation area of the API Designer, click Implementations.
  3. Click JavaScript Scaffold to download the zip file.
    If Git integration has been enabled, the JavaScript Scaffold button is replaced by a drop-down list. In this case, click JavaScript Scaffold and then select Download (if you want to download the scaffold) or Push to Git Repository (if you want to immediately start working with the scaffold in Git).
  4. On your system, unzip the downloaded file.

Note:

If you later change the API, then you can download a new scaffold based on the updated endpoints. However, any coding that you may have done and uploaded previously won’t be reflected in the new scaffold.

Writing Custom Code

The following sections show the constructs that are available to you and how to use them in your code.

Key JavaScript Constructs in Custom Code

The scaffold zip that you download from the API Designer includes a main JavaScript file, which contains the key constructs that you need to implement the custom API. Here’s an example of a main JavaScript file for a custom API, which has these resources (URIs):

  • /incidents, which supports the GET and POST HTTP methods

  • /incidents/:id, which supports the GET HTTP method

  • /incidents/:id/uniquecode, which supports the GET HTTP method


// A
module.exports = function(service) {


  //B
	service.post('/mobile/custom/incidentreport/incidents', function(req,res) {
		var result = {};
		res.send(201, result);
	});

	service.get('/mobile/custom/incidentreport/incidents', function(req,res) {
		var result = {};
		res.send(200, result);
	});

	service.get('/mobile/custom/incidentreport/incidents/:id', function(req,res) {
		var result = {};
		res.send(200, result);
	});

	service.get('/mobile/custom/incidentreport/incidents/:id/uniquecode', function(req,res) {
		var result = {};
		res.send(200, result);
	});

};

This example illustrates these main constructs:

  • (A) module.exports = function (service) {implementation}

    The module.exports statement is required at the beginning of all custom API implementations. It’s used to export an anonymous function with a parameter (service) through which the custom code service passes the object that’s used to expose your endpoints. The service parameter is an instance of an Express application object, and all the object’s functionality is available. Note that in Express example code, this parameter is often called app. The anonymous function contains all the API’s route definitions.

  • (B) Route definitions

    A route definition is an Express route method that associates an anonymous callback function with an endpoint (resource plus HTTP method). Its signature takes the following form:

    service.HttpMethod('URI', function (req, res)
    
    • service is the variable for the custom code service instance (or, in Express terminology, the application instance), which was defined in the module.exports = function (service) statement.

    • HttpMethod is one of the following methods corresponding to standard REST methods:

      • get

      • delete

      • head

      • options

      • patch

      • post

      • put

    • URI refers to resource defined in the API. Notice that while braces identify a parameter in the API design for the resource, you use a colon to identify a parameter in the uri. For example, if the resource is /incidentreport/incidents/{id}, then you use '/mobile/custom/incidentreport/incidents/:id' for the URI.

    • function (req, res) is a callback through which HTTP request and HTTP response objects are passed. It defines how the API responds to client requests to that endpoint. The req variable provides access to the data in the request and you can use the res variable to build the result. Node and Express provide properties and functions for those two variables, which enable you to retrieve information about their values and work with them. We talk about some of these next.

      For more information about the req and res objects, see http://expressjs.com/4x/api.html#request and http://expressjs.com/4x/api.html#response.

    The following example is a route definition for the endpoint GET /incidentreport/incidents/{id}/uniquecode, which generates a unique code.

    service.get(
      '/mobile/custom/incidentreport/incidents/:id/uniquecode',
      function (req, res) {
        console.info('get /incidentreport/incidents/' +
          req.params.id + '/uniquecode');
        res.type('application/json');
        // status defaults to 200
        res.send({'code': req.params.id + '-' + new Date().valueOf()});
      });

    Notice that the code example uses req.params.id to get the :id value from the URI. Here are some of the request properties that you typically use in your code:

    Property Description
    req.body If the request’s content type is application/JSON or application/x-www-form-urlencoded then this property contains the data that was submitted in the request body in the form of a JavaScript object. For information about accessing other types of request bodies, see Accessing the Body of the Request.
    req.headers A map of header names and values. The names are lower case. Often used to transport extra information in the request, such as an external identifier.
    req.params An object that contains properties that map to parameters in the endpoint’s URI. For example, if the endpoint is attachments/:collection/objects/:objectid, then you use req.params.collection and req.params.objectid to get the parameter values.

    When you use the req.params object to retrieve a parameter value, you must use the same case as the parameter in the endpoint. For example, if the endpoint parameter is {id}, then you must use req.params.id to get the value, and not req.params.Id.

    req.query The query string parameters that are passed in the URI. For example, if the request is GET /incidents?q=joe&order=desc then you use req.query.q and req.query.order to get the query parameters.

    Here are some methods that you typically use to inquire about the request:

    Method Description
    req.get(field) and req.header(field) Both these methods return the value for the header named by field. For example, req.header('content-type'). The match is case-insensitive.

    Note that req.header is an alias for req.get.

    req.is(mimeType) Boolean method that you can use to find out if the request’s Content-Type header matches the mimeType. For example, req.is('json').

    Note:

    The custom code service essentially creates Express application objects and then configures them with service-specific functionality (such as identity propagation and consolidated logging) before it passes them to the custom API implementation logic for further configuration. You get preconfigured Express application objects to which you add route-specific business logic.

Here we discussed only the basic usage of Express features necessary to implement the API by using routing methods to set up callbacks. However, the entirety of the Express features are available for use in custom code. Consult the Express documentation at http://expressjs.com/ to learn about the details, such as how to implement URI parameter parsing, set up multiple callback handlers, and use third-party middleware.

Accessing the Body of the Request

When requests that are received by the custom code have a content type of application/x-www-form-urlencoded or application/json, the payload is converted to a JavaScript object, which is then stored in req.body. For all other types, such as image/jpeg or text/html, req.body is undefined. Examples of when this occurs is when the body is a text file or an image. In those cases, when you need to access the body from the incoming request’s handler, use the data event listener and end event listener to save the body to a buffer.

The following example shows how to access the body for different content types:

if (req.is('json') || req.is('application/x-www-form-urlencoded'))
    {
       console.info('Request Body: ' + JSON.stringify(req.body));
    } else {
        var data = [];
        // Process a chunk of data. This may be called multiple times.
        req.on('data', function(chunk) {
          // Append to buffer
          data.push(chunk);
        }).on('end', function() {
          // process full message here
          var buffer = Buffer.concat(data);
          // Convert to base64, if required
          // var base64 = buffer.toString('base64');
      });
   }

To learn more about Node.js events and listeners, see https://nodejs.org/api/events.html#events_events.

Inserting Logging Into Custom Code

You can use the Node console object to add logging messages to custom code, as shown in this example:

console.info(i + ' Request to get ' + url);

These messages appear in the diagnostic logs.

The custom code service wraps the console object to enable finer-grained logging. The following methods are available for logging messages at different levels:
  • console.severe

  • console.warning

  • console.info

  • console.config

  • console.fine

  • console.finer

  • console.finest

By carefully applying log levels to the messages in your code, you can simplify how you debug and administer the app. This allows you to add good debug messages, and then log them only as necessary, such as during development or when diagnosing a problem. For example, you might want to add the following log messages at the suggested log levels:

Log Message Log Level
Function entry and exit Finest
Input, such as parameters that are sent with the request Fine
Caught exceptions Severe
Uncaught exceptions Fine

To set the level at which logging is enabled for a backend, from either the mobile backend’s diagnostics page or the Administration page, click Logs, and then click Log Level (Log Level).

To learn how to view the logs, see Accessing Logging Messages for Custom Code.

Note:

Node.js has a less granular set of native methods for logging, which are also possible to use. The logging level of the native Node.js methods console.log and console.dir is equivalent to console.info. The Node.js method console.warn is equivalent to the custom-code method console.warning. The Node.js method console.error is equivalent to the custom-code method console.severe.

When you use console messages to locate problem code, know that the service’s console calls are nonblocking. That is, there’s no guarantee that logging completes before the next statement is executed. In the case of a problem that’s caused by an infinite loop, you will most likely see only the first console message that’s in the block of code before the infinite loop. Consider the following code, for example:

console.info("Log 1");
var myVar="any string";
console.info("Log 2");
myVar="a different string";
console.info("Log 3");
functionWithInfiniteLoop();

When this code is executed, it’s possible that only Log 1 appears in the diagnostic logs. Therefore, to locate an infinite loop, you must have just one console message, and you must put that message where you think it will flag the problem. If it doesn’t flag the problem, then move the message and run another test until you identify the problem code.

When you suspect an infinite loop, follow these steps:

  1. Remove or comment out all console messages.

  2. Add a logging statement as the last line before the return.

  3. Ensure that the log level for your backend is set to the same level as your logging statement, such as INFO for a console.info() message.

  4. Test the endpoint.

  5. Look in the diagnostic logs for your logging statement.

  6. If you don’t see the message, move the logging statement up one line and test the endpoint again.

  7. Repeat the previous step until the message appears in the log.

    At this point, you know that the problem statement is just below the logging statement.

Tip:

If you have several lines of code, then you can reduce the number of tests by putting the logging statement in the middle of the code block and then testing the endpoint. If you don’t get the log message, then put the logging statement in the middle of the top half. Otherwise, put the logging message in the middle of the bottom half. Test the endpoint. Repeat the test by dissecting the code blocks until you have narrowed the test to just two lines of code.

Storing Data Locally

Don’t use the file system that’s associated with the virtual machine running the Node.js instance to store data, even temporarily. The virtual machines that run Node.js instances might fluctuate in number, meaning that data written to one instance's file system might be lost when individual instances are started and stopped.

To store data from custom code, you can use the Database Access API, which is described in Database, or the Storage API, which is described in Accessing the Storage API from Custom Code.

Video: Working with Node - Common Code

For a demonstration of writing Node code to implement custom APIs, take a look at the Oracle Mobile Platform video series on custom code, starting with this video:

Implementing Synchronization-Compatible APIs

If your mobile app uses the Synchronization library to enable offline use, as described in Data Offline and Sync, then here’s some information about how to make your implementation compatible with the library.

Note:

To learn how to design your API so that it is compatible with the Synchronization library, see Endpoint Requirements for Sync Compatibility and API Design Considerations.

Video: Working with Custom APIs via Data Offline & Sync

If you want an overview of how to build your custom API to have synchronization-compliant REST endpoints and data, take a look at this video:

Requirements for a Synchronization-Compatible Custom API

To ensure that the Synchronization library can synchronize with your custom API’s data, as described in Building Apps that Work Offline Using the Synchronization Library, follow these rules:

Method Response Body Response Headers Response HTTP Status Codes
GET
  • To return a single item, use setItem() to put the item in the response, as described in Returning Cacheable Data. Note that this method adds the Oracle-Mobile-Sync-Resource-Type header to the response and sets it to item.

  • To return a collection, use addItem() to add the items to the collection, as described in Returning Cacheable Data. Note that this method associates each item with its required URI and ETag and sets the Oracle-Mobile-Sync-Resource-Type header to collection.

    If there’re no items in the collection, then you must return a body with empty items, uris, and etags arrays. For example:
    {
       items:[],
       uris:[],
       etags:[]
    }
  • Oracle-Mobile-Sync-Resource-Type: Must be set to item for a single item, or collection for an array of items. The setItem() and addItem() methods set this header automatically for items and collections. If the response body is a file, you optionally can set this header to file.

  • ETag: If the Oracle-Mobile-Sync-Resource-Type header is set to item or file, then this header must be set to the item’s ETag (in quotes).

  • Oracle-Mobile-Sync-Evict, Oracle-Mobile-Sync-Expires, and Oracle-Mobile-Sync-No-Store: Optional. See Specifying Synchronization and Cache Policies.

No special requirements
PUT If the item stored on the server is different from the item in the request body, such as having a different ID in the case of an add or containing automatically calculated fields like modifiedOn, then return the stored item in the response body. Otherwise, returning the item in the response body is optional.
  • Location: If the item was added, then you must include this header, which contains the item’s URI. Otherwise, this header is optional.

  • ETag: Must contain the item’s ETag in quotes.

  • Oracle-Mobile-Sync-Resource-Type: Must be set to item for a single object. The addItem() method sets this header automatically. If the response body is a file, you optionally can set this header to file.

  • Oracle-Mobile-Sync-Evict, Oracle-Mobile-Sync-Expires, and Oracle-Mobile-Sync-No-Store: Optional. See Specifying Synchronization and Cache Policies.

Note that the value in the If-Match header value dictates the actions to take and the response code to send. The Synchronization library sends * in the If-Match header when the conflict resolution policy is CLIENT_WINS. For all other conflict resolution policy configurations (that is, SERVER_WINS and PRESERVE_CONFLICT), it sends the item’s ETag. If the header isn’t present or is null, then assume *.

  • If there’s an If-Match header and its value isn’t *, then, if the item’s ETag doesn’t match the header’s value, return 412 Precondition Failed.

  • If the item to be updated no longer exists, then do one of the following:
    • If the If-Match header is *, then add the item and return 201 CREATED

    • If there’s an If-Match header and its value isn’t *, then return 404 NOT FOUND.

  • If the item was successfully updated, then return one of the standard PUT codes, such as 200 OK or 204 No Content.

POST

If the item stored in the server is different from the item in the request body, then include the stored item in the response body. Otherwise, returning the item in the response body is optional. For example, if the server adds calculated fields such as createdOn, then return the stored item in the response body.

  • Location: Must contain the item’s URI.

  • ETag: Must contain the item’s ETag in quotes.

  • Oracle-Mobile-Sync-Resource-Type: Must be set to item for a single object. The addItem() method sets this header automatically. If the response body is a file, you optionally can set this header to file.

  • Oracle-Mobile-Sync-Evict, Oracle-Mobile-Sync-Expires, and Oracle-Mobile-Sync-No-Store: Optional. See Specifying Synchronization and Cache Policies.

No special requirements
DELETE No special requirements No special requirements
  • If there’s an If-Match request header and its value isn’t *, then if the ETag of the item to be deleted doesn’t match the header’s value, return 412 Precondition Failed.

    Note that the Synchronization library sends * in the If-Match header when the conflict resolution policy is CLIENT_WINS. For all other conflict resolution policy configurations, it sends the item’s ETag.

  • If the item doesn’t exist, then you can return either a 404 Not Found or a 204 No Content. The Synchronization library process is the same for both codes.

  • If the item was successfully deleted, then return one of the standard DELETE codes, such as 200 OK, 202 Accepted, or 204 No Content.

If you want to learn more about how the Synchronization library uses the 412 Precondition Failed HTTP response status code and the If-Match header to implement conflict resolution policies, see Synchronization Library Process Flow. Basically, if the conflict resolution policy is CLIENT_WINS, then the If-Match header is set to * to indicate that the server must update or delete the resource without conflict. Otherwise, the If-Match header is set to the item’s ETag, and the custom code is expected to return 412 Precondition Failed if the ETags don’t match.

Tip:

Most methods require an ETag header in the response, and many methods require that you compare the server version’s ETag with the value in the request’s If-Match header. There are several node libraries that you can use to create ETags. For example, the NPM etag library that is available from https://www.npmjs.com/package/etag.

Returning Cacheable Data

The custom code SDK provides the following methods to format your data for use by the Synchronization library. Using these methods enables the library to optimize synchronization.

oracleMobile.sync Method Description
setItem(response, item) Set the response body to the item.
addItem(response, item, uri, etag) Add the item to a collection, which will be returned in the response body in a cacheable format.
clear(response) Undoes all calls to setItem and addItem .

For a response with a single JSON object, you use setItem to format the data, as shown in this example, and you return the ETag value in the ETag header:

var etag = require('etag');
...
service.get(
'/mobile/custom/incidentreport/incidents/:id/uniquecode',
function (req, res) {
    res.setHeader('Etag', etag(JSON.stringify(payload));
    req.oracleMobile.sync.setItem(
       res,
       {'code': req.params.id + '-' + code}
    );
    res.end();
});

For a JSON object that contains an array of items, you use addItem to add each item to the response, as shown in the next example. Note that addItem attaches a URI and an ETag value to each item in the response body. The URI must uniquely identify each item.

var etag = require('etag');
...
service.get(
  '/mobile/custom/incidentreport/statusCodes',
  function (req, res) {
    var payload = {'inroute': 'Technician is on the way'};
    req.oracleMobile.sync.addItem(
      res,
      payload,
      '/mobile/custom/incidentreport/statusCodes/inroute',
      etag(JSON.stringify(payload))
      );
    payload = {'arrived': 'Technician is on premises'};
    req.oracleMobile.sync.addItem(
      res,
      payload,
      '/mobile/custom/incidentreport/statusCodes/arrived',
      etag(JSON.stringify(payload))
      );
    payload = {'completed': 'Technician has left premises'};
    req.oracleMobile.sync.addItem(
      res,
      payload,
      '/mobile/custom/incidentreport/statusCodes/completed',
      etag(JSON.stringify(payload))
      );
    res.end();
  });

The response body for the addItem example looks like this:

{
  "items": [
    {
      "inroute": "Technician is on the way"
    },
    {
      "arrived": "Technician is on premises"
    },
    {
      "completed": "Technician has left premises"
    }
  ],
  "uris": [
    "/mobile/custom/incidentreport/statusCodes/inroute",
    "/mobile/custom/incidentreport/statusCodes/arrived",
    "/mobile/custom/incidentreport/statusCodes/completed"
  ],
  "etags": [
    "\"26-5vTpRVIO9SakJoLYEQrQ0Q\"",
    "\"27-+lktOY9aA46ySRE0O/y5Aw\"",
    "\"2c-PSRg8Cxr2rYp/9BftCmDag\""
  ]
}

When you use setItem and addItem, the response also includes this header:

Header Description Type
Oracle-Mobile-Sync-Resource-Type If the response body is JSON, then the value is item if the JSON object includes a single item. The value is collection if the JSON object contains an array of items. Note that when the response is a file, you optionally can set the value to file. When this header isn’t included in the response, the Synchronization library assumes that the type is file. That is, when this header is not set, then the MobileResource that the Synchronization library fetchObjectBuilder and fetchCollectionBuilder methods return is of type MobileFile. String

Specifying Synchronization and Cache Policies

For the mobile apps that use the Synchronization library, you might want to override their settings for whether to cache the data that you return and when to expire and delete the data. For example, if the data contains private information, you might want to prevent a mobile app from caching that data. This table shows the Oracle-Mobile-Sync HTTP headers to override these settings.

Header Description Type
Oracle-Mobile-Sync-Evict

Specifies the date and time after which the expired resource should be deleted from the app’s local cache. Uses RFC 1123 format, for example EEE, dd MMM yyyyy HH:mm:ss z for SimpleDateFormat.

The following synchronization policies are set for the resource object that is created from the response:

  • Eviction policy: EVICT_ON_EXPIRY_AT_STARTUP

  • Expiration policy: EXPIRE_AFTER with the expireAfter property set to date and time provided in the header value

    .
Number
Oracle-Mobile-Sync-Expires Specifies when to mark the returned resource as expired. Uses RFC 1123 format, for example EEE, dd MMM yyyyy HH:mm:ss z for SimpleDateFormat. Number
Oracle-Mobile-Sync-No-Store When set to true, instructs the client to not cache the resource. Boolean

Calling Web Services and APIs from Custom Code

Your custom code will most likely need to access one or more of the following types of APIs and services:

  • Platform APIs: Your custom code can connect with platform services, such as Storage, Notifications, and Location, through their APIs.

  • Custom APIs: Your custom code can interact with all the other custom APIs that are in your environment.

  • Connector APIs: Your custom code can serve as wrappers for connector APIs.

  • External web services: Typically, you create connector APIs with which to interact with external services, but you also can connect with remote web services directly from custom code.

Calling APIs from Custom Code discusses how to access platform, custom, and connector APIs from custom code.

If you need to make a third-party web service call that doesn’t require you to shape the data, and you don’t need integrated diagnostics, tracking, and analytics for that call, then you might choose to call the service directly instead of setting up a connector. You can call a web service directly from your custom code using Node APIs such as the HTTP API. For information about the Node HTTP API, see nodejs.org/api/http.html.

Note that HTTP and HTTPS are the only supported protocols for making calls to the Internet from custom code.

Note:

If the third-party web service changes its API, then a connector requires just one change, whereas with direct calls, you must make sure you find and change all the direct calls. Also, consider that if you’re testing against a test web service, you’ll have to modify the URLs for the direct calls when you switch to the production service.

Packaging Custom Code into a Module

After you’ve written custom code to implement an API, and before you upload and deploy it, follow these steps to package the implementation:
  1. Declare the implementation version in the package.json manifest file.

  2. Optionally declare the Node version in the package.json file.

  3. Declare in the package.json file the API dependencies on other modules.

  4. Run the Node.js package manager (npm) to download the dependencies.

  5. Put all the implementation files in a zip file.

Required Artifacts for an API Implementation

An API implementation is packaged as a zip archive containing, at minimum, the following artifacts:

  • A root directory that has the name of the custom code module.

  • The package.json file. Within this file, you specify in JSON format the name of the module and any dependencies that your custom code has, such as any connector APIs. See package.json Contents for information on the contents and syntax on the package.json file.

    Note:

    By Node convention, this file must be within the root directory.

  • At least one JavaScript file that contains the implementation code.

  • If there are any additional modules that you are using (in addition to Express and the base Node features), then a node_modules directory containing those modules. See Packaging Additional Libraries with Your Implementation.

package.json Contents

Like all npm packages, custom API implementations require that you identify the project and its dependencies in a package manifest named package.json. Here’s an example of the syntax and the properties of a package.json file for a custom API implementation:

{
  "name" : "incidentreports",
  "version" : "1.0.0",
  "description" : "FixItFast Incident Reports API",
  "main" : "incidentreports.js",
  "dependencies": {
    "async": "0.9.0"
  },
  "oracleMobile" : {
    "dependencies" : {
      "apis" : {"/mobile/custom/employees" : "3.5.1"},
      "connectors" : {"/mobile/connector/RightNow": "1.0"}
    }
  }
}

The key attributes are the following:

name

A descriptive name for the implementation. The name can contain only characters that can be used in a URI. It may not start with a period (.) or underscore (_). The value of this attribute in combination with the value of the version attribute must be unique among all API implementations.

version

The version of the implementation. If you provide a new version of an implementation, then this attribute should be incremented and the name value should stay the same.

description

An optional description of the implementation.

main

The name of the main JavaScript file that implements the API. If this file isn’t in the same folder as the package.json file, then use a path name that’s relative to the package.json folder.

dependencies

The specification of dependencies to other Node modules required for the implementation. When you have such dependencies, use npm to install those modules in this directory. See Packaging Additional Libraries with Your Implementation.

oracleMobile / dependencies / api

The specification of the version for a custom API or a connector API that you reference in your custom code.

Declaring the API Implementation Version

Use the version attribute in the package.json file for the custom code module to specify the implementation version, as shown in the following example:

{
  "name" : "incidentreport",
  "version" : "1.0.0",
  "description" : "Incident Report Custom API",
  "main" : "incidentreport.js",
  "oracleMobile" : {
    "dependencies" : {
      "apis" : { },
      "connectors" : {"/mobile/connector/RightNow": "1.0"}
    }
  }
}

If you have previously uploaded an implementation and that implementation is still in Draft state, then you can continue to upload modified implementations without incrementing the version number. After you publish a version, that version is final. If you want to make changes to a published implementation, then you must increment the version number.

You can publish implementations independently of APIs, and you can increment their version numbers separately as well. This lets you make changes to a published implementation, such as minor modifications or bug fixes, without requiring the API itself to be updated.

To create another version of an API implementation, change the version attribute, such as "version": "1.0.1", and then upload a zip file of the modified implementation. When you upload a new version of an implementation, it becomes the default version (active implementation) for that API. You can change the default version in the API’s Implementations page.

If the new version is backward-compatible, then use a minor incremental increase. For example, if the previous version is 1.3, then the updated version number could be 1.4 or 1.7. If the new version isn’t backward-compatible, then use a major incremental increase. For example, if the previous version is 1.3, then the updated version number could be 2.0 or 2.1.

For more information about publishing and deploying APIs, see Lifecycle Scenarios.

Declaring the Node Version

To use a version of the Node library other than the instance’s default version, add a node property to the configuration section as shown in the following example:

{
  "name" : "incidentreport",
  "version" : "1.0.0",
  "description" : "Incident Report Custom API",
  "main" : "incidentreport.js",
  "oracleMobile" : {
    "configuration" : {
      "node" : "6.10"    }
  }
}

To learn about the default Node version and the available node versions, see What's the Foundation for the Custom Code Service?.

Packaging Additional Libraries with Your Implementation

If your API implementation depends on other JavaScript modules, such as Async, then you must add them to your custom code zip file. The additional modules aren’t shared across APIs. For example, you must include the Async module in every implementation package that uses it. Your implementation can't use any modules that depend on installing a binary (executable) on the server.

Note:

You don’t need to add the Request and Express modules unless you explicitly use them in your code. You don’t need to add the Bluebird module if the only operation that you use is the then operation.
  1. In the package.json file for the implementation module, declare the modules that the implementation module depends on. Specify both the module name and the version number in the following format:
    "dependencies": {
         "Module1Name":"VersionNumber",
         "Module2Name":"VersionNumber",
    },
  2. In the directory containing the package.json file for the custom code module, run:
    npm install
    

    This command downloads the stated dependencies from the public npm repository and places them in the node_modules subdirectory.

    Note:

    If the module on which you’re creating the dependency is in a folder on your file system instead of in the public npm repository, add the path to the folder as an argument to the command:

    npm install folder-name 

    For more information on using the npm package manager, see https://www.npmjs.org/doc/cli/npm-install.html.

  3. Package the whole folder containing the package.json file in a zip archive.

Uploading the Custom Code Module

  1. On your system, prepare the required artifacts for the implementation, as described in Required Artifacts for an API Implementation.
  2. From the API Catalog, open the custom API that the custom code implements.
  3. In the left navigation bar, click Implementations.
  4. At the bottom of the API Implementation page, click Upload an implementation archive, and then go to the implementation zip file on your system.

Managing Custom Code in Git

When you first generate a JavaScript scaffold for your implementation, you can have it pushed directly to a Git repository.

Setting Up a Git Repository for Custom Code

As a mobile cloud administrator, you can specify a Git repository in which to store your team’s custom API implementations. This enables your team’s developers to put their API implementations under version control starting with the creation of the JavaScript scaffold for the API.

You can use either an existing Git repository or create a new one in Oracle Developer Cloud Service to use. To do the latter, see Using Git in Oracle Developer Cloud Service in Using Developer Cloud Service.

Designating a Git Repository for Custom Code
If you already have a Git repository set up that you want to use for you team’s custom code, you can specify that repository as the place where new JavaScript scaffolds are pushed. To designate the Git repository, you need to have access permission for the Administration page.
You can see if you have access to that page by clicking icon to open the side menu and checking to see if there is an item for Administration as shown in the screenshot below.
  1. Click icon to open the side menu to open the side menu and select Administration.
  2. Click the link for Developer Cloud Service Git Integration.
  3. In the dialog, enter the URL for the Git repository in the Source Code Git Repository field and click Save.
Setting Up a Git Repository in Oracle Developer Cloud Service

If you don’t already have a Git repository for your custom code, you can set one up in Oracle Developer Cloud Service.

  1. Make sure that you have a user account in Oracle Developer Cloud Service.

    If you have a user account, you will have received an email notification with your account details.

    If you don’t have a user account, contact your team’s identity domain administrator to set up an account for you (and for anybody else who will be using the repository). You need the Developer Service User role (DEVELOPER_USER) in Oracle Developer Cloud Service.

  2. Sign in to Oracle Developer Cloud Service.

  3. Set up a project to hold the Git repository by following the steps at Creating a Project in the Oracle Developer Cloud Service docs.

    As part of creating the project, a Git repository will be created.

See Managing Git Repositories in Oracle Developer Cloud Service.

Generating a Scaffold in a Git Repository

  1. Click This is an image of the sidebar menu. to open the side menu, and then click APIs.
  2. Open the API that you want to implement.
  3. In the left navigation area of the API Designer, click Implementations.
  4. Click New Implementation > Push to Git Repository.
You can now clone the repository (or pull from the repository if you have already cloned it) to fill in the scaffold with your custom code.

Testing and Debugging Custom Code

You can test and debug your custom code directly within the UI. It’s also possible to test your custom code outside of the UI.

Testing with Mock Data

When you create a custom API, you get a mock implementation, which application developers can use to test their mobile applications while you are implementing the custom code. When you call an endpoint for a mock implementation, it returns the request example, if one has been provided.

The mock implementation is the default implementation until you upload an implementation. Whenever you upload an implementation, it is automatically deployed as the default implementation. You can always change this, including reverting to the mock implementation, for testing purposes. To change the default implementation, select it on the Implementations page and click Set as Default.

You can create example (mock) data to provide default request and response bodies for the test UI. You can use either the API Designer or the RAML to add example (mock) data. To provide an example for an endpoint from the API Designer, from the Endpoints page, go to the desired method, click either the Requests tab or the Responses tab, select the appropriate media type, and then enter the mock data in the Example tab.

Here is an example of providing mock data in the RAML.

/status:
      get:
        description: |
          Gets status of specified report.         
        responses:
          200:
            description: |
              OK.             
            body:
              application/json:
                example: |
                  { "code": "New",
                     "notes": "My hot water tank's model is AB234"
                  }

Testing Custom Code from the UI

As soon as you upload your custom API implementation, you can test it by clicking Test in the API Designer. You can also test from the API Catalog by selecting the API, and then clicking Test.

The test page displays all the operations. Click an operation, fill out the necessary fields, and then click Test Endpoint.

If the API isn’t configured for anonymous access, then you must provide a user name and the password. The user must have been assigned one of the roles that can access the endpoint. If the endpoint doesn’t have any roles configured for it, then the user must belong to a role that’s associated with the API. You can learn more about roles and anonymous access at Security in Custom APIs.

Note:

The API must either allow anonymous access or be associated with at least one role. If neither of these is true, then you’ll get an unauthenticated error.

For detailed steps on how to test the API, see Testing a Platform or Custom API from the UI.

Offline Debugging with the MCS Custom Code Test Tools

Within the zip file of the client SDK for each platform is the mcs-tools.zip file, which contains the MCS Custom Code Test Tools that you can use to iteratively debug your custom code.

The core of the tools is an npm module that enables you to run an offline custom code container, run tests on the code, and package and deploy an implementation back to MCS.

Detailed instructions on using the tools are located in the README.MD file that is packaged within the mcs-tools.zip.

You can get the client SDKs and the accompanying test tools from the Oracle Technology Network’s MCS download page.

Other Tools for Testing Custom Code Outside of the UI

Besides the Custom Code Test Tools, you can use tools that were designed for testing web services, such as cURL, Postman, and SoapUI (which tests both SOAP and REST). To learn how to test your custom API from these tools, see Testing Platform and Custom APIs Remotely.

Note:

The API must either allow anonymous access or be associated with at least one role. If neither of these is true, then you will get an unauthenticated error.

Accessing Logging Messages for Custom Code

When your API implementation doesn’t return the expected results, use the diagnostic logs to troubleshoot the problem.

To pinpoint where the error occurred, click This is an image of the sidebar menu. to open the side menu. Next, click Administration, and then click Request History. Next, find the request, click View related log entries View Related Log Entries icon in the Related column, and then select Log Messages Related by API Request. To see a message’s details, click the time stamp. From the Message Details dialog, you can click the up and down arrows to see all the related log messages.

You can get to the Request History page from either the Administration page or a mobile backend’s Diagnostics page. Note that if there isn’t sufficient information in a request to enable the service to determine the associated backend, then the related log messages appear only in the Logs page that is available from the Administration page.

Every message is tagged with a request correlation ID that associates all messages for a request. When you view a message’s details, you can click the request correlation ID to see the other messages for the same request.

If you don’t see any messages that help identify the source of the problem, then you can change to a finer level for logging messages. Click Log Level Log Level icon in the Logs page, change the log level for the mobile backend, and then rerun the test.

To learn about the different types of log messages and how to filter and correlate messages, see Viewing Log Messages. For use cases for diagnosing custom code and connector issues, see Diagnosing Custom Code.

Let's use the following endpoint to see how to custom code logging works. In this code, the ums.updateUser method makes a PUT request to /platform/users/~.

service.put(
  '/mobile/custom/incidentreport/key',
  function (req, res) {
    req.oracleMobile.ums.updateUser({key: req.body.key}).then(
      function (result) {
        res.send(result.statusCode, result.result);
      },
      function (error) {
        res.send(error.statusCode, error.error);
      }
    );
  });

The service always logs a message whenever a call ends, regardless of the log level setting. In the following figure, the bottom (earliest) message was logged when the PUT request to /platform/users/~ ended. The top (later) message was logged when the service.put call to /mobile/custom/incidentreport/key ended.


Description of custom-code-loginfo.png follows
Description of the illustration custom-code-loginfo.png

Logging Request and Response Messages

If you would like to see the bodies of the requests and responses, then ask your mobile cloud administrator to change the CCC_LogBody environment policy to true. When you do this, the service logs a CCC message whenever a body is passed in a request or a response. The following figure shows the log entries for a request to the example endpoint. In addition to the standard call messages, this log also has a message for each of the following events (reading from the bottom (earliest) up):

  • When the PUT /mobile/custom/incidentreport/key request was received.

  • When the ums.updateUser method made the PUT /platform/users/~ request .

  • When the response was returned by the PUT /platform/users/~ operation.

  • When the response was returned by the PUT /mobile/custom/incidentreport/key operation.

When you set the log level to Info, the service logs the request bodies with a message type of INFO. Response bodies are logged with a message type that corresponds to the response status. For example, if the response status is 401, then the log message that contains the response body has a message type of WARNING.


Description of custom-code-loginfo-body.png follows
Description of the illustration custom-code-loginfo-body.png

Note that setting the CCC_LogBody environment policy to true might have a negative effect on performance.

Note:

By default, the body is truncated after 512 characters. Use the CCC_LogBodyMaxLength environment policy to change the maximum body length. To always include the full message, no matter how long it is, set CCC_LogBodyMaxLength to -1.

Getting More Details

To get the maximum amount of log messages, set the log level to FINEST. With this level, the service logs the following messages:

  • A FINEST message, which contains the HTTP verb and URI, whenever a request is received by any of the custom API’s endpoints

  • A FINEST message, which contains the HTTP verb, URI, and status code, whenever a response is sent by any of the custom API’s endpoints

  • A FINEST message, which contains the HTTP verb and URI, whenever a request is sent to another platform or custom API.

  • A FINEST message, which contains the HTTP verb, URI, and status code, whenever a response is received from a call to another platform or custom API.

If the CCC_LogBody environment policy is set to true and the log level is FINEST, then the following occurs:

  • If a request body exists, then the FINEST message that contains the request’s HTTP verb and URI also shows the body.

  • If a response body exists and the response status code is less than 400, then the FINEST message that contains the HTTP verb, URI, and status code for the response also shows the body.

  • If a response body exists and the response status code is 400 or higher, then the response body is logged in a separate message. Immediately after, it logs the FINEST message for the response. The message type is either WARNING or SEVERE, depending on the status code.


Description of custom-code-logfinest-body.png follows
Description of the illustration custom-code-logfinest-body.png

Note that setting the log level to FINEST might have a negative effect on performance.

Minimizing the Performance Cost of Logging Bodies

If you are concerned about the performance cost of logging bodies, but you want to see the request and response bodies for exceptional cases, set the CCC_LogBody environment policy to true, and set the logging level to WARNING or SEVERE. With these settings, whenever there is a status code of 400 or higher, a message is logged for both the request and the response. Both messages are logged at the time that the response is received. The message type is WARNING or SEVERE, depending on the status code. The message shows the body, if there is one.
Description of custom-code-log-warning-body.png follows
Description of the illustration custom-code-log-warning-body.png

Creating Custom Log Messages

To help with debugging, you can use the console object in your code to generate your own messages, as described in Inserting Logging Into Custom Code, and then view them from the logs.

Troubleshooting Custom API Implementations

The following topics provide information about diagnosing and resolving common problems in custom code.

Diagnosing Syntax Errors

If a request failure is caused by a syntax error, then the Message Detail dialog box for the associated log message displays the module and line number where the error occurs, as shown here:


Description of custom-code-syntax-error.png follows
Description of the illustration custom-code-syntax-error.png

To learn about accessing log messages, see Viewing Log Messages.

If you’d like to see the stack traces for custom code syntax errors in request responses, then ask your mobile cloud administrator to change the CCC_SendStackTraceWithError environment policy to true. When you do this, you’ll see a request response like the following example whenever a request results in a syntax error in the custom code. The stack trace shows the line number where the error occurred.

{"message": "Custom Code Problem: ReferenceError: nonExist is not defined\n at /scratch/aime/mobile/mobile_ccc/custom_code_modules/ccc2455344468806884059/incidentreports/incidentreports.js:354:17\n at callbacks (/scratch/aime/mobile/mobile_ccc/mcs-node-router/node_modules/express/lib/router/index.js:164:37)\n  ..."
}

Common Custom Code Errors

The following topics discuss common errors, possible causes, and solutions.

Custom Code Problem parsing JSON: Error: request entity too large

The typical cause of this error is an image in the request body that is larger than the default maximum input size. With Node configurations prior to 6.10.0, the maximum size is 1 MB. The maximum size for the Node 6.10.0 configuration is 100KB.

If your environment uses the Node 6.10.0 configuration, then you can do one of the following to resolve the problem:

  • Switch the Node configuration for the custom API implementation to the prior 0.10 configuration by adding a node property to the configuration section in the custom API implementation's package.json file as described in Declaring the Node Version. The Node version in the package.json file overrides the CCC_DefaultNode environment policy for that custom API implementation.

  • Ask your mobile cloud administrator to change the appropriate CCC_DefaultNode environment policy to the prior 0.10 configuration. Any custom API implementation that uses that default configuration will change to use the 0.10 configuration no later than its second REST request after the version change.

  • Change the JSON body parser limit:

    1. From the directory that contains the package.json file, run the following command to install the body-parser module:

      $ npm install body-parser
    2. In the custom API implementation’s package.json file, add a dependency to the body-parser module as shown in the following example:

      {
        ...
        "dependencies": {
           "body-parser": "1.15.2"
        }
      }
    3. In the implementation’s main JavaScript file, add the following code to increase the maximum size:

      var bodyParser = require('body-parser');
      module.exports = function(service) {
        service._router.stack[3].handle = bodyParser.json({limit: '1mb'});

For information about Node configurations, see What's the Foundation for the Custom Code Service?

Custom Code Problem in oracleMobile.rest callback: Argument error, options.body

The common cause for this error is assigning a JavaScript object to optionsList.body, where optionsList is the first parameter in a call to req.oracleMobile.rest.post(optionsList, handler).

The solution is to do one of the following:

  • Store the object in options.json, instead of optionsList.body. This solution automatically converts the object to a JSON string and sets relevant parts of the request, such as the content type and length. For example:

    optionsList.json = {first: 'John', last: 'Doe'};
  • Use JSON.stringify to convert the object to a JSON string before setting the optionsList.body value. For example:

    optionsList.body = JSON.stringify(first: 'John', last: 'Doe'}; 
    optionsList.headers = {'Content-Type': 'application/json');

Your custom code container is in the process of recovering from an unhandled error in a earlier request

This issue occurs when a previous request results in an uncaught exception. When you receive this response, rerun the current request. It should succeed as soon as the system has recovered from the uncaught exception for the previous request.

You should examine the logs for the previous requests to see if you can find the cause of the uncaught exception.

Connection fails due to untrusted URL

To protect client apps, the service passes all external URLs through McAfee Web Gateway v7.x/6.9.x (Cloud), which requires that all external URLs are trusted. This requirement applies to external service URLs for connector APIs as well as those that you access directly from custom code.

Attempting to connect with an untrusted connector endpoint results in a 403 error, which might be wrapped in a 500 error.

To resolve the issue, add the untrusted URL to the list of trusted URLS for McAfee Web Gateway v7.x/6.9.x (Cloud) at http://trustedsource.org/.  Note that the process can take from three to five business days.

database.getAll(table, options, httpOptions) doesn’t return all the rows in a table

This issue occurs when there are more rows in the table than the Database_MaxRows environment policy allows the service to return. The default value is 1000.

Ask your mobile cloud administrator to increase the Database_MaxRows value.

This mobile user doesn't have the necessary permissions to call this endpoint

In the UI, open the API and click Security. If Login Required is turned on and Enterprise is selected, then look at the roles that have been configured. If no roles are configured, then no one has permission to log in to the mobile backend. If one or more roles are configured, ensure that the user has a necessary role.

What Happens When a Custom API Is Called?

You might be curious about how the service handles calls to a custom API. Here is a high-level summary. When the service receives a custom API request, it sends the request to the custom code service. The custom code service then directs the request to one of the following:

  • Custom code container for the API implementation: A container is a Node instance. This container wraps the custom API implementation with JavaScript that handles tasks such as server startup, authentication, authorization, and logging. There is one container for each deployed version of an implementation for each associated mobile backend version.

  • Custom code agent: The agent controls the creation and destruction of custom code containers, controls server startup, and exposes the REST endpoints for creating and destroying a container.

Basically, a custom API implementation is launched on demand in a container that is instantiated by the custom code agent. This container, which runs in Node, handles the requests and returns the responses.

When the custom code calls a platform API or a connector API, it makes the call back through the service, and then the service routes the call to that API. If the call is to a different custom API, then the service routes the call to that API’s container if it exists, or it creates the container and then routes the call to it.