FlexServices

Kinvey Flex is a framework for writing server-side code for your mobile and web apps. It allows you to create low-code, lightweight Node.js microservices to use for data integrations, authentication, and general-purpose functions. Flex services utilize the Flex SDK and can consist of FlexData for data integrations, FlexFunctions for trigger-based data pre/post hooks or custom endpoints, and FlexAuth for custom authentication through Mobile Identity Connect (MIC).

You can include FlexData, FlexAuth, and FlexFunctions handlers all in the same Flex service, but it is recommended to separate them.

Looking for a guided tutorial to help you build your first Flex service? Check out this article on the Progress blog.

Types of Flex Services

Depending on where you choose to deploy you Flex Service, you can have two types of Flex services:

  • Internal Flex services are projects that you deploy on the FlexService Runtime. The FlexService Runtime is cloud-based infrastructure managed by Kinvey. It is a controlled, highly-available environment that requires you to develop against specific software versions. This type of deployment is recommended for most customers as it spares you the hassle of maintaining your own servers and planning for high availability, load balancing, and scaling.
  • External Flex services are projects that you deploy on server infrastructure managed by you. It is suitable to customers who require the service to run on-premises or on another type of trusted infrastructure. Subsequently, implementing high-availability, disaster recovery, load balancing, and scaling remains your responsibility.

The Flex SDK

The Kinvey Flex SDK emits events within your Node.js service when collection data is requested from your Kinvey app. These events are paired with user-defined handlers that fetch and transform data (from any number of sources) into Kinvey collections. The Flex SDK also includes modules for interacting with other Kinvey APIs (such as push notifications, email, collections access, and more).

FlexData

You can use FlexData to connect a data source that you own or manage to Kinvey. Your apps are then able to consume the data it holds as if it were stored in a Kinvey collection, using the same Kinvey SDK methods.

To connect a data source, you need to create a Node.js service that implements all the data access logic and then deploy it as either an internal or an external Flex service.

After you deploy the Flex service, you access the data from the data source by mapping it to Kinvey collections.

Similarly to any Flex service, FlexData services are Node.js projects that use the Kinvey Flex SDK. Apart from it, you can use any npm module that you see fit. The one exception are modules with external dependencies for projects deployed internally on the FlexService Runtime. For example, if you want to use the oracledb package to connect to Oracle Database, you will need to deploy as an external Flex service because this will allow you to install the required Oracle Client alongside Node.js.

Initializing FlexData

You can access the FlexData framework through the Flex SDK's data property.

const flexData = flex.data;

Registering Service Objects

To allow access to data in the system you are integrating with, you need to register a service object. A service object is a generic term for data objects or records on a remote system that Kinvey can interact with—for example, a database table.

Use the serviceObject() method of the data object to define service objects. Afterwards, you can wire up data access event handlers to those service objects.

// To register the "widgets" service object:
const widgets = flex.data.serviceObject('widgets');

After you deploy your FlexData service, you will be able to map service objects to Kinvey collections. This allows you to work with data from the FlexData service using the same APIs you use for cloud-based Kinvey collections.

You can deploy a Flex service either as an internal or an external service.

Handling Data Events

Each service object exposes data events that are invoked by Kinvey collections. The data event takes a single handler function to be executed.

Some of the data events include onInsert, onDeleteById, or onDeleteAll. See the Flex API Reference for the full list.

The following example retrieves all entities in the widgets service object:

widgets.onGetAll(callbackFunction);

Data Handler Functions

The data events take a handler function, which takes three arguments: context, complete, and modules. The context argument represents the current context of the request made to Kinvey, and complete is a completion handler for completing the data request. The modules argument is an object containing several libraries for accessing Kinvey functionality via your service (for more information, see the section on modules).

Accessing the Context

The context object represents the state of the respective stage of the Kinvey request pipeline. It contains properties such as the HTTP method, the request headers, the body, the query, and so on.

See the Flex API Reference for full description.

Completing the Handler

The completion handler initiates the complete process. It follows a builder pattern for creating the handler's response. The pattern for the completion handler is complete()[.setBody(<entity>).<status>.done()|next()]

For example, a sample completion handler is:

complete(myEntity).ok().next();

In previous versions of the Flex SDK, entities were passed in the complete() method. This functionality is deprecated and will be removed in a future version of the SDK.

It provides a set of methods for altering the context of the request and a series of status functions. For the data step, the only relevant context-altering method is setBody(), which takes an entity, an array of entities, an error description, or an Error object.

Note that a body must be explicitly set using setBody. The entity must be a JSON object or a JSON array for successful responses. For example:

// Sets the response to include an entity
complete().setBody({'foo':'bar'});

// Sets the response to include an array of entities
complete().setBody([{'foo':'bar'}, {'abc':'123'}]);

The setBody method is not required. If you want to return an empty object, simply call complete(). If you need to pass an empty array, you must call setBody() with the empty Array.

// Sets the response to an empty object
complete();

// Sets the response to an empty array
complete().setBody([]);

For errors, you can either pass a string as the error message, or a JavaScript Error object.

// Sets the response to an error string to be used with error status functions
complete().setBody('Record 123 was not found');

// Sets the response to an Error object to be used with error status functions
complete().setBody(new Error('Record 123 was not found');

Status functions set the valid status code for a FlexData operation. The status function also sets the body to a Kinvey-formatted error and uses the value passed into the status function or the setData() function as the debug property, if it is present.

See the Flex API Reference for the available status functions.

For example:

// Return that the record has been created
complete().setBody(myRecord).created();

// Entity wasn't found
complete().setBody('The given entity wasn\'t found').notFound();

After the status is set, you can end the processing of the handler request with either done() or next(). Most requests should normally end with next(), which continues the Kinvey request pipeline. The done() function returns the response set in the completion handler and ends the request processing without executing any further functions.

// Continue the request chain
complete().setBody(myEntity).ok().next();

// End the request chain with no further processing
complete().ok().done();

Example

The following code provides a more complete example of how to structure a FlexData service.

const sdk = require('kinvey-flex-sdk');
sdk.service((err, flex) => {
  const data = flex.data;   // gets the FlexData object from the service
  function getRecordById(context, complete, modules) {
    let entityId = context.entityId;
    let entity = null;

    // Do some logic to get the entity id from the remote data store
    // Assume that data is retrieved and stored in "entity" variable

    // After entity is retrieved, check to see if it exists
    if (typeof entity === 'undefined' || entity === null) {
      return complete().notFound('The entity could not be found').next();
    } else {
      // return the entity
      return complete().setBody(entity).ok().next();
    }
  }

  // set the serviceObject
  const widgets = data.serviceObject('widgets');

  // wire up the event that we want to process
  widgets.onGetById(getRecordById);
});

FlexFunctions

The FlexFunctions framework is used to execute functions invoked by hooks or endpoints.

Initializing FlexFunctions

You can access the FlexFunctions framework through the Flex SDK's functions property.

const functions = flex.functions;

Registering FlexFunctions Handlers

To register a FlexFunctions handler, you define that handler and give it a name by using the register() method of the functions object.

The following code snippet registers the someEventHandlerName handler:

flex.functions.register('someEventHandlerName', (context, complete, modules) => {
  //eventHandler logic
});

In the Kinvey Console, when you define hooks or endpoints, you will be presented with the list of event handler names you've registered. You will be able to connect them to a collection hook or endpoint.

Handler Function

Flex functions take a handler functions with three arguments: context, complete, and modules. The context argument represents the state of the current Kinvey request context and complete is a completion handler for completing the Flex function. The modules argument is an object containing several libraries for accessing Kinvey functionality through your service (for more information, see the section on modules).

In previous versions of the Flex SDK, entities were passed in the complete() method. This functionality is deprecated and will be removed in a future version of the SDK.

Accessing the Context

The context object represents the state of the respective stage of the Kinvey request pipeline. For example, for events that are executed before a data request (pre-hooks), the context contains the request body and query.

See the Flex API Reference for full description.

Completing the Function

The completion handler initiates the complete process. It follows a builder pattern for creating the handler's response. The pattern for the completion handler is complete().[setBody(<entity>).setQuery(<query>).<status>.done()|next()].

An example completion handler is:

complete().setBody(myEntity).setQuery(myQuery).ok().next();

It provides a set of methods for altering the context of the request and a series of status functions.

FlexFunctions support a couple of context-altering methods:

MethodDescription
setBody()Sets the data entity or entities to be passed to the next step of the pipeline or as the final result. Optional. Takes an entity, an array of entities, an error description, or an Error object. The entity must be a JSON object or a JSON array.
setQuery()Replaces the query object with an altered query object. Only useful in Before collection hook functions.
// Sets the context to include an entity and an altered query
complete().setBody({'foo':'bar'}).setQuery({query: {'foo':'bar'}});

// Sets the response to include an array of entities
complete().setBody([{'foo':'bar'}, {'abc':'123'}]);

The setBody() method is not required. If you want to return an empty object, simply call complete(). If you need to pass an empty array, you must call setBody() with the empty Array.

// Sets the response to an empty objectS
complete();

// Sets the response to an empty array
complete().setBody([]);

For errors, you can either pass a string as the error message or a JavaScript Error object.

// Sets the response to an error string to be used with error status functions
complete().setBody('Record 123 was not found');

// Sets the response to an Error object to be used with error status functions
complete().setBody(new Error('Record 123 was not found');

Status functions set the valid status code for a Flex function operation. The status function also sets the body to a Kinvey-formatted error and uses the value passed to the status function or the setBody() function as the debug property, if it is present.

See the Flex API Reference for the available status functions.

For example:

// Return that the record has been created
complete().setBody(myRecord).created();

// Entity wasn't found
complete().setBody('The given entity wasn\'t found').notFound();

After the status is set, you can end the processing of the handler request with either done() or next(). Most requests should normally end with next(), which continues the Kinvey request pipeline. The done() function returns the response set in the completion handler and ends the request processing without executing any further functions.

// Continue the request chain
complete().setBody(myEntity).ok().next();

// End the request chain with no further processing
complete().ok().done();

Example

The following code provides a more complete example of how to structure a FlexFunctions service.

const sdk = require('kinvey-flex-sdk');
const request = require('request'); // Assumes that the request module
                                    // was added to package.json.
sdk.service(function(err, flex) {

  const flexFunctions = flex.functions; // Gets the FlexFunctions object
                                        // from the service.

  function getRedLineSchedule(context, complete, modules) {
    request.get(
        'http://developer.mbta.com/Data/Red.json',
        (err, response, body) => {
            // if error, return an error
            if (err) {
                return complete().setBody('Could not complete request')
                    .runtimeError().done();
            }

            //otherwise, return the results
            return complete().setBody(body).ok().done();
    });

   }

  // set the handler
  flexFunctions.register('getRedLineData', getRedLineSchedule);
};

FlexAuth

The FlexAuth framework is used to execute custom authentication handlers for logging in through Mobile Identity Connect.

Initializing FlexAuth

You can access the FlexAuth framework through the Flex SDK's auth property.

const auth = flex.auth;

Registering FlexAuth Handlers

To register a FlexFunction handler, you define that handler and give it a name by using the register() method of the auth object.

The following code snippet registers the someEventHandlerName handler:

// To register the 'someEventHandlerName' handler
const mySSO = flex.auth.register('mySSO', eventHandlerCallbackFunction);

In the Kinvey Console, when you define a FlexAuth service, you will be presented with the list of authentication event handler names you've registered.

Handler Function

Each FlexAuth handler takes a handler functions with three arguments: context, complete, and modules. The context argument represents the state of the current Kinvey request context and complete is a completion handler for completing the function. The modules argument is an object containing several libraries for accessing Kinvey functionality through your service (for more information, see the section on modules).

In previous versions of the Flex SDK, entities were passed in the complete() method. This functionality is deprecated and will be removed in a future version of the SDK.

Accessing the Context

The context object represents the state of the respective stage of the Kinvey request pipeline. Some of the object properties are set only at the appropriate stages.

See the Flex API Reference for full description.

Completing the Function

The completion handler initiates the complete process. It follows a builder pattern for creating the handler's response. The pattern for the completion handler is complete().[setToken(<token>).addAttribute(<key>, <value>).removeAttribute(<key>).<status>.done()|next()].

An example completion handler is:

complete().setToken(myToken).addAttribute('userEmail', myEmail).ok().next()

It provides a set of methods for altering the context of the request and a series of status functions.

FlexAuth supports the following context-altering methods:

MethodDescription
setToken(token)Sets the authentication token to be used for this user.
addAttribute(key, value)Adds custom attributes to your FlexAuth response. Optional.
removeAttribtue(key)Removes a previously added custom attribute from your FlexAuth response. Optional.

Note that you must set a token explicitly using setToken(). The value must be a JSON object or a Base64-encoded string.

// Sets the context to include an entity and an altered query
complete().setToken({'myAuthToken': 'ffds9afdsafdsaf89ds0fds90f8ds-='})
    .addAttribute('email', 'test123@test.com');

For errors, you can either pass a string as the error message or a JavaScript Error object.

// Sets the response to an error string to be used with error status functions
complete().setToken('Record 123 was not found');

// Sets the response to an Error object to be used with error status functions
complete().setToken(new Error('Record 123 was not found');

Status functions set the valid status code for a FlexAuth operation. The status function also sets the body to an OAuth-formatted error and uses the value passed to the status function as the debug property, if it is present.

See the Flex API Reference for the available status functions.

For example:

// Return that the user has been authenticated
complete().setToken(myToken).ok();

After the status is set, you can end the processing of the handler request with either done() or next(). Most requests should normally end with next(), which continues the Kinvey request pipeline. The done() function returns the response set in the completion handler and ends the request processing without executing any further functions.

// Continue the request chain
complete().setToken(myToken).ok().next();

// End the request chain with no further processing
complete().ok().done();

Example

The following code provides a more complete example of how to structure a FlexAuth service.

const sdk = require('kinvey-flex-sdk');
const request = require('request'); // Assumes that the request module
                                    // was added to package.json.
sdk.service((err, flex) => {

  const flexAuth = flex.auth; // Gets the FlexAuth object
                              // from the service.

  function authenticate(context, complete, modules) {
    // authenticate the user here
    if (err) {
      return complete().accessDenied(err).next();
    }
    return complete().setToken(token).ok().next();
  }

  // set the handler
  flexAuth.register('myAuth', authenticate);
};

Flex Routes

Kinvey exposes a set of HTTP endpoints, called Flex routes, for each FlexService that you create. These endpoints are your FlexService's façade visible to apps that connect to your backend.

There is a separate set of Flex routes for FlexData, FlexFunctions, and FlexAuth, as well as a route for service discovery.

You need to implement an event handler for each route in your FlexService.

Routes for FlexData

The FlexData routes allow you to implement CRUD and count operations. See the Flex API Reference for a list of FlexData routes.

Routes for FlexFunctions

FlexFunctions exposes a route for each FlexFunctions function you create. The route name depends on the name you have given the function. See the Flex API Reference for details.

Routes for FlexAuth

FlexAuth exposes a route for each FlexAuth function you create. The route name depends on the name you have given the function. See the Flex API Reference for details.

Routes for Service Discovery

The service discovery route is an auxiliary route that returns all registered Flex objects on your Kinvey account. This includes FlexData service objects, FlexFunctions, and FlexAuth endpoints.

See the Flex API Reference for details.

Executing a Long-running Script

Because the services are persisted, you can execute long running tasks that run in the background. However, all Kinvey requests still need to send a response within 60 seconds. For internal Flex services, the FlexService Runtime sends an automatic timeout error even earlier.

To accomplish this, you can execute a function asynchronously using one of the Timer functions from Node.js: setImmediate, setTimeout, or setInterval.

If you decide to use long-running scripts, you should know that your code can still be interrupted by our system. We regularly install important security updates and sometimes do other maintenance that may require us to restart your Flex service. Additionally, the Flex Service Runtime might sometimes idle your service.

Those interruptions are infrequent, but you must not rely on your code running forever. You should always write your background code in a way that tolerates interruptions.

For example:

const sdk = require('kinvey-flex-sdk');
const request = require('request'); // Assumes that the request module
                                    // was added to package.json.
sdk.service(function(err, flex) {

  const flexFunctions = flex.functions; // Gets the FlexFunctions object
                                        // from the service.

    function calcSomeData() {
        // do something
    }

  function calcAndPostData() {
    auth = {
      user: '<MY_APP_KEY>',
      pass: '<MY_APP_SECRET>'
     };

    options = {
        url: 'https://baas.kinvey.com/appdata/<MY_APP_KEY?/someCollection',
        auth: auth,
        json: {
            someData: calcSomeData(),
            date: new Date().toString()
        }
     }

    request.get(options, (err, response, body) => {
      // if error, return an error
      if (err) {
        return console.log('Error: ' + err);
      }

      //otherwise, just return
      return;
    });

  }

  function initiateCalcAndPost(context, complete, modules) {
    // Since the calc and post data function may take
    // a long time, execute it asynchronously.
    setImmediate(calcAndPostData);

    // Immediately complete the handler function. The response will be returned
    // to the caller and calcAndPostData will execute in the background.
    complete().accepted().done();

  // set the handler to point to the initiateCalcAndPost header
  flexFunctions.register('getRedLineData', initiateCalcAndPost);
};

In the above example, the handler receives a request and executes the initiateCalcAndPost function. The function schedules an immediate asynchronous execution of calcAndPostData, and then executes the complete handler, returning control to the client and sending the response (in this case, accepted, because the request is accepted for processing). The service stays running in the background, and the long-running calcAndPostData function is executed asynchronously.

Keep in mind that callbacks specified in timer functions such as setImmediate, setInterval, and setTimeout do not get the request metadata or the execution context automatically. If the request metadata is required during your code execution, for example for logging or debugging, you must explicitly pass this data to the callback function.

Modules

Modules are a set of libraries intended for accessing Kinvey-specific functionality. The optional modules argument is passed as the third argument to all handler functions. For example:

// data handler
function onGetById(context, complete, modules) {
  const appKey = modules.backendContext.getAppKey();
  // etc...
}

You can use any non-Kinvey library or module by including it in your package.json as you would for a standard Node.js project. The Flex SDK provides the following Kinvey-related modules:

  • backendContext Provides methods to access information about the current backend context.
  • dataStore Fetch, query, and write to Kinvey collections.
  • email Send Email notifications
  • groupStore Fetch, query, and write to Kinvey groups.
  • Kinvey Entity Kinvey entity utilities
  • kinveyDate Kinvey date utilities
  • push Send push notifications to a user's device
  • Query Create queries to be used by the dataStore.
  • requestContext Provides methods to access information about the current request context.
  • tempObjectStore Key-value store for persisting temporary data between a pre- and post-hook.
  • userStore Fetch, query, create, update, delete, suspend and restore Kinvey Users.
  • roleStore Fetches, queries, and writes Kinvey user roles. Lists role members.
  • endpointRunner Calls custom endpoints with reusable user context and authentication.

Click each link to view the API reference information for the respective module.

Logging

In addition to the native JavaScript logging facilities like console.log(), the Flex SDK offers a set of its own logging functions. Using these functions may be beneficial when running an external Flex service as they always persist log messages on the server.

After you've collected some logs, you can use Kinvey CLI to read and filter through them. See the flex log subcommand for details.

Retention Policy

Flex.logger log entries are stored on the Kinvey servers and are subject to some restriction as described bellow.

  • The number of log messages is capped at 500,000 entries per Flex service.
  • The cumulative size of all messages cannot exceed 100 MB per Flex service.

If any of the restrictions is reached, the system starts deleting the oldest entries to make room for new entries.

Keep in mind that the capacities above are shared between Flex.logger and other output that is generated during the operation of a Flex service such as stdout.

Logging Messages

The logger module provides a different logging function for each severity level: info(), warn(), error(), fatal(). Each of them takes a string argument representing the log message. You can also pass an instance of Error directly and it will be automatically stringified.

The following example defines a function that deletes entities older than a specified number of days. It reads the number of days from the request body which is accessible through the context. The logger functions are used three times: to log an info-level message indicating each function execution, an error-level message containing the stingified error in case of failure, and another info-level message with the response body in case of success.

function deleteOldItems(context, complete, modules) {
    const logger = flex.logger;
    logger.info('Executing deleteOldItems Flex function');

    const deleteBefore = context.body.deleteBefore;
    const query = new modules.Query();
    query.lessThanOrEqualTo('_kmd.ect', deleteBefore);

    const bookStore = modules.dataStore().collection("Books");
    bookStore.remove(query, function(err, count) {
        if (err) {
            logger.error('Delete failed: ' + JSON.stringify(err));
            complete().setBody(err).runtimeError().done();
        } else {
            const response = {'Deleted records': count};

            logger.info('Deletion succeeded: ' + JSON.stringify(response.body));
            complete().setBody(response).ok().done();
        }
    });
}