Business Logic

New apps should utilize Flex exclusively for server-side logic. Business Logic is documented as a reference for existing apps.

Introduction

Kinvey Business Logic is a node.js code execution runtime that allows you to customize the behavior of requests to your backend by providing a means to implement server-side business logic. Kinvey Business Logic enables developers to focus on the unique functional business logic of a mobile application, while Kinvey handles the work of receiving, parsing, and routing requests, connecting to and querying data stores, and enabling mobile-specific functionality.

Business Logic is commonly used to enable:

  • Triggering messages such as email and push notifications on developer-defined changes in the data store.
  • Optimizing the data exchange with the app by joining multiple data collections.
  • Validating app user actions to enforce business rules.
  • Providing a means to host platform-agnostic code and save development time on multi-platform apps.

Business logic can be invoked as part of a request to a Kinvey collection, as a custom endpoint to execute an arbitrary function, as common code created and shared among BL functions, and on a scheduled basis.

This guide provides an introduction to the core concepts of Kinvey Business Logic. For a complete reference to the APIs exposed by Kinvey Business Logic, see the Business Logic API Reference.

Core Concepts

The Elements of a Business Logic Script

A business logic script in Kinvey is a single function that is executed as part of a client request, either as a hook to a collection request, or as a custom endpoint that is either directly invoked or scheduled to execute at a specific time or on a repeating basis. All Business Logic functions are passed three arguments that can be used as part of your business logic:

ArgumentDescription
requestAn object representing the request that came into Kinvey
responseAn object representing the response that will be sent back to the client
modulesA series of API modules that can be used to manipulate or action on a request

The request and response objects allow you to manipulate what comes in and goes out of the function. For example, in the below simple Hello World example, the response body is set to the contents of the request body, plus one additional key named hello with the value of world.

function onRequest(request, response, modules) {
  var requestBody = request.body; // this gets the JSON body of the request
  response.body = request.body;
  response.body.hello = 'world';
  response.complete(200);
}

So if the request sent to the client contained the body

{ 
  "name":"Bob",
  "department": "sales"
}

then the response received from the client would be

{
  "name":"Bob", 
  "department":"sales",
  "hello":"world"
}

The modules argument provides a variety of libraries that can be used within your business logic. These include, but are not limited to:

  • Kinvey-specific modules for accessing different elements of the Kinvey platform
  • Control-flow modules such as async.js and the Bluebird promises library, used for controlling node.js asynchrony.
  • Utility libraries for working with dates
  • Data Validation libraries
  • User engagement libraries for sending emails and push notifications

For a complete list of the APIs exposed by Kinvey Business Logic, see the Business Logic API Reference.

Best Practices

Business logic scripts are short-running scripts that are intended to augment the default functionality of Kinvey. When writing server-side business logic, it is important to take total request time into account, in order to provide a good user experience for the end-user.

While Kinvey Business Logic has a built-in timeout value that is measured in seconds, code should be written in such a way as to keep the total request time as low as possible in order to maintain a good user experience. In general, users can perceive any delay that is greater than 100 milliseconds, and most users consider anything that takes longer than one second to be slow. While it is sometimes necessary to perform tasks beyond these numbers, long-running requests should be kept to a minimum and the goal should be short, responsive API calls. To help keep requests short, you should:

  • Make use of node.js asynchrony. Multiple asynchronous requests (such as collection access, external HTTP requests, etc.) should be parallelized if possible.
  • Make use of the general collection hook flow when possible rather than custom endpoints. For example, if you need to combine the results from two collections, make a query request to the first collection, and query the second collection in a post-fetch hook.
  • Avoid looping through large recordsets using for or forEach. Instead, use the async module's each, eachSeries, and eachLimit functions along with node's process.nextTick / setImmediate.
  • Keep business logic scripts short and atomic.
  • Avoid returning large data sets to the mobile client. Mobile devices are optimized, both from a user experience perspective and a bandwidth perspective, to digest limited, relevant data sets rather than syncing entire copies of tables and/or collections.

Types of Business Logic

Business Logic is triggered in one of three ways:

  • Collection Hooks, which are triggered by a request to any Kinvey collection and allow for server-side logic to occur pre/post fetch, pre/post save, pre/post insert, pre/post update and pre/post delete.
  • Custom Endpoints, which are triggered on demand through a custom HTTP endpoint or via a function call in the client libraries.
  • Scheduled Code: A means of executing a custom endpoint on a scheduled basis.

In addition, common code provides a means of creating a library of reusable functions that can be invoked from any collection hook or endpoint.

Collection Hooks

Collection hooks allow you to add server-side logic to Kinvey's collection request pipeline. Understanding this pipeline is essential for the effective use of Collection Hooks.

Configuring Collection Hooks

All Kinvey collection requests go through a similar processing sequence.

  1. Verify request is authenticated
  2. Perform any Before processing Collection Hook Business Logic
  3. Do internal Kinvey processing (this includes ACL verification, database interaction, data integration, etc)
  4. Perform any After processing Collection Hook Business Logic
  5. Format and send response to the client

You are able to insert your own code as either a Before or After processing action on a Kinvey collection.

To configure a collection hook via the console, find your collection under the Collection Hooks list in Business Logic. Use the Add Hook button next to the collection name to add a hook.

Kinvey Business Logic

Kinvey Business Logic

For complete request API documentation see Kinvey Requests in the Business Logic API reference.

Invoking Collection Hooks

To invoke a collection hook, access the collection for which the hook is defined, and the appropriate hook for your action will be executed. For example, if you have defined an After Fetch hook for a collection named myCollection, your code will run every time data is retrieved (fetched) from the myCollection collection.

To know whether collection hooks were executed for a specific request, Kinvey will return an X-Kinvey-Executed-Collection-Hooks header as part of the response, containing a string with a comma-separated list of hooks (i.e. "Pre-Fetch, Post-Fetch" or "Post-Save"). If this header is not included in the response from Kinvey, then no collection hooks were executed as part of your request.

Login Hooks

The User collection is a special collection that, apart from the regular collection hooks, has specific "pre" and "post" hooks related to the user login event.

  • onPreLogin - executed before a user login request.
  • onLoginSuccess - executed after a successful user login:
    • after a login request
    • after a sign up request with API version 1 to 5. In those API versions the sign up request automatically logs in the newly created user
  • onLoginFailure - executed after a failed user login:
    • after a login request
    • after a sign up request with API version 1 to 5 and only if the sign up request error is a result to failure to issue an access token

The login hooks processing flow

The login hooks will be executed whenever a user is logged in (a session token for the user is created and returned). This can happen with a login request or a user sign-up (create) request with API version 1 to 5.

Login request

Login request with login hooks

From the above diagram, we can see that:

  • login hooks would not fire when the request itself fails to be authenticated - this can happen if the request's Authorization header is wrong.
  • in case the onPreLogin execution returns an error response (it fails to execute because of a BlTimeoutError or a Syntax error) or the custom code returns a status code greater or equal to 400, then the login process would be stopped, and a response is returned right away. As a result, the onLoginSuccess or onLoginFailure hooks would not fire.
Login request with MFA enabled

When user login requires Multi-factor authentication (MFA), the login flow changes from a single request to 3 separate requests. The login hooks would fire in the following sequence: Login request with MFA and login hooks

User sign-up request

When a user sign-up request is made (only for API version 1 to 5), the newly created user is automatically logged in, and a user session token is returned with the sign-up request. The flow looks like this: Sign-up request with login hooks

From the above diagram, we can see that:

  • onPreLogin is not executed in this case as this request is not, in essence, a login request.
  • if the request authentication fails (a wrong Authorization header is used), the process is stopped. As a result, a response is returned right away.
  • if the request fails to create a user in the database, the onLoginSuccess or onLoginFailure would not fire.
  • the onLoginSuccess would fire if the creation of a user and issuing of a user session token succeeds.
  • the onLoginFailure would fire if the creation of a user is successful, but the request fails at issuing a user session token.

As of API version 6, the sign-up endpoint no longer logs in the newly created user (no user session token is returned with the request). As a result, when using API version 6 or higher, the sign-up method would not trigger any login hooks.

Difference with other collection hooks

Login hooks have a few differences compared to other collection hooks.

  • Instead of one post hook, there are two different "post" hooks - onLoginSuccess and onLoginFailure.
  • loginOptions property is available in the request context. The login options would hold the following information:

    • loginOptions.type - indicate whether the login is with a Kinvey user (kinvey), MIC (kinveyAuth) or another provider - linkedIn for example.
    • loginOptions.mfaRequired - indicate with true or false whether a multi-factor authentication is required for this user. This property is visible in onLoginSuccess and onLoginFailure hooks.
  • No tempObjectStore between onPreLogin and post login hooks (onLoginSuccess or onLoginFailure).

  • The Custom Request Properties object will be available in onLoginSuccess and onLoginFailure only if the login does not use multi-factor authentication.

onLoginFailure

Unlike other collection hooks where the "post" login hook is fired only when the database operation succeeds, the onLoginFailure hook is fired if the login process fails.

The hook holds detailed information about the failing request. The following code would log the status code and the returned error:

function onLoginFailure(request, response, modules){
  var logger = modules.logger;

  logger.info(response.status);
  logger.info(response.body);

  response.continue();
}

Here is what the logged info would look like if the login error is due to invalid credentials:

401
{
  "error": "InvalidCredentials",
  "description": "Invalid credentials. Please retry your request with correct credentials.",
  "debug": ""
}

The login process can fail for various reasons:

CodeErrorDescription
400InvalidRequestBodyInvalid or expired code.
400IncompleteRequestBodyThe body of the request is missing credentials required to login
400IncompleteRequestBodyNo request body. Login credentials expected in the request body
400IncompleteRequestBodyNo supported identity provider found
400AuthenticatorNotVerifiedThe authenticator is not verified. Please specify a verified authenticator and retry the request.
400MissingRequestParameterThe request must specify a code in the body.
400MissingRequestParameterRequired field: authenticatorId. Please try again and include it in the request.
400FeatureUnavailableTwo-factor authentication is required for this app but it is available from API version
400FeatureUnavailableTwo-factor authentication is enabled for this user but it is available from API version
400BadRequestCan only login with only one external identity provider at one time
401InvalidCredentialsInvalid credentials. Please retry your request with correct credentials.
401InvalidCredentialsThe specified recovery code is invalid.
401InvalidCredentialsSpecified credentials are not allowed to create a session
401UserLockedDownAccess with these credentials has been disabled by an administrator.
401EmailVerificationRequiredThis app requires email address verification. Before performing any operations, please verify your email address by clicking the link sent to you upon signup.
403MissingConfigurationNo authenticator has been chosen.
500KinveyInternalErrorRetryThe Kinvey server encountered an unexpected error. Please retry your request.

Invoking login hooks

You have to create a login hooks - choose the "user" collection. Then in order to invoke the login hooks, simply call a login request or a user sign-up (create) request.

When using Flex to execute a login hook, please use flex-sdk v4.1.0 or greater.

Login hooks examples

Allow login with MIC only
Case-insensitive login on the server
Notify on successful login
Notify on login failure

Custom Endpoints

Custom Endpoints provide for the on-demand execution of server-side business logic. Custom Endpoints can be initiated via a REST request, or from the client libraries.

Configuring Custom Endpoints

To configure a custom endpoint via the console, go to the Endpoints list in Business Logic. Use the Add Endpoints button next to add an endpoint. You will be prompted to give the endpoint a name:

Kinvey Business Logic

This will bring you to the editor, where you can input your business logic code.

Kinvey Business Logic

Invoking an Endpoint

Custom endpoints are accessed through the CustomEndpoint.execute() class method.

  • the 1st parameter (unnamed) is the name assined to your custom endpoint.
  • the params should be a Dictionary<String, Any> (JSON dictionary). This is passed to the endpoint as the request.body. For example, if params were:
let params = [ "name" : "Fred Jones", "eyes" : "blue" ]

and the endpoint logic were

function onRequest(request, response, modules){
    modules.logger.info(request.body.name + " has " + request.body.eyes + " eyes.");
    response.continue();
}

will cause the following to be written to the logs:

Fred Jones has blue eyes.
  • the completionHandler will have either valid results if the response is completed with a success code or a Error with either the error returned by the custom code or Kinvey (such as in a timeout or runtime error).

The following example calls an endpoint called tagsNearMe:

let json: [String : Any] = [
    "city" : "Boston"
]
let params = CustomEndpoint.Params(json)
CustomEndpoint.execute("tagsNearMe", params: params, options: nil) { (result: Result<[JsonDictionary], Swift.Error>) in
    switch result {
    case .success(let jsonArray):
        print(jsonArray)
    case .failure(let error):
        print(error)
    }
}

You can map your own types to use as a parameter or as a result. Parameters can be: [String : Any] (a.k.a JsonDictionary) or a CustomEndpoint.Params which also supports JsonDictionary or your own type. To use your type you have to create your own class or struct and extend from Mappable or StaticMappable using the ObjectMapper dependency framework. For example:

struct GeolocationParam : Encodable {

    var latitude: Double?
    var longitude: Double?

    init(_ latitude: Double, _ longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
    }

}

struct Address: Decodable {

    var city: String?
    var state: String?
    var country: String?

}

let params = GeolocationParam(42.3134791, -71.1271964)
CustomEndpoint.execute("locationDecode", params: try CustomEndpoint.Params(params), options: nil) { (result: Result<[Address], Swift.Error>) in
    switch result {
    case .success(let addressArray):
        print(addressArray)
    case .failure(let error):
        print(error)
    }
}

Common Code

Common code can be used to create reusable functions that can be used across your business logic scripts. To configure common code via the console, go to the Common list in Business Logic. Use the Add Common button next to add common code. You will be prompted to give the common code a name:

Kinvey Business Logic

An example of a common code function that adds two numbers is:

function add(a, b) {
  return a + b;
};

The add function can now be invoked from any collection hook or custom endpoint

function onRequest(request, response, modules) {
  // some BL code here
  var a = 1;
  var b = 2;
  var c = add(a,b);
  // more BL code here
}

Scheduled Code

Custom endpoints can be scheduled to run at specified times. You can select a start date and time as well as recurrence interval (such as weekly or every 30 minutes).

To schedule an endpoint, navigate to BUSINESS LOGIC > Scheduled Code and click the Add Scheduled Endpoint button at the top right of the screen.

Keep in mind that the start date and time is in your local computer time and time zone, as shown conveniently in the dialog window. If you need to schedule the job to run at a specified time in another time zone, don't forget to factor in the time offset.

Creating a second scheduled endpoint for the same custom endpoint overwrites the existing scheduled endpoint.

Kinvey Business Logic

You can find more examples in the Get Started with Scheduled Code tutorial.

Scheduled jobs have the following limitations:

  • A single environment of a Kinvey app cannot have more than 50 jobs.
  • Inactive scheduled endpoints (such as one-time jobs that have already been executed) are deleted 60 days after their last execution.

Writing Business Logic

Business Logic (BL) provides a Node.js runtime for executing your server-side code. The code executes inside a container that is destroyed immediately after use. All Node.js core libraries are available, but some (such as file access, console access, setting up an HTTP or TCP server, etc.) have no practical effect due to the ephemeral nature of Business Logic. Additionally, Kinvey currently does not support the addition of custom npm modules to BL code. Otherwise, the full Node.js stack is available within the context of a collection hook or endpoint.

From time to time, Kinvey will add specific whitelisted npm modules (such as async.js, moment.js, and Bluebird) to Business Logic, when it is applicable to the general Kinvey community. If there is a module that would be valuable to your app, you can submit it to support@kinvey.com for consideration. Please note that modules are only added if they have general applicability.

Understanding Asynchrony

Because it is based on Node.js, Kinvey Business Logic makes heavy use of asynchronous JavaScript patterns. In general, calls that require I/O (database access, web requests, etc.) are executed asynchronously while code execution continues. Upon completion of the asynchronous call, a callback function is invoked. Consider the output of the following example code:

function onRequest(request, response, modules) {
  modules.request.get('http://www.somedomain.com', function(err, result) {
    modules.logger.info("Request finished");
    return response.complete();
  });

  modules.logger.info("Continuing code execution");

}

Log output:

Continuing Code Execution
Request Finished

This is because as soon as the call is made to invoke a request to http://www.somedomain.com, code execution continues with the statement to log "Continuing code execution". Once the request is finished, the statement "Request finished" will be logged, and BL execution will be completed.

This asynchronous callback mechanism allows for the optimization of request time but can also lead to many errors. For example, if we moved the return statement in the above code as follows, the callback for the request would never be invoked. Outside of asynchronous calls with callbacks, code in Node.js executes synchronously to completion.

function onRequest(request, response, modules) {
  modules.request.get('http://www.somedomain.com', function(err, result) {
    modules.logger.info("Request finished");
});

  modules.logger.info("Continuing code execution");
  return response.complete();
}

Completing Business Logic

As shown above, JavaScript code often uses callbacks that are invoked when processing completes. Since Kinvey can't know when a database or network request will complete, Kinvey doesn't know when your logic is done running. To counter this you must return either response.complete, response.continue, or response.error when your logic is finished executing.

  • return response.complete(statusCode)—Stop processing this request further and return response to the client. The HTTP status code is set to statusCode.
  • return response.continue()— Continue normal Kinvey request processing. This is only valid within collection hooks and is not available for custom endpoints.
  • return response.error(error)—Stop processing this request further and return an HTTP 400 error to the client with a user-defined error message. Details on user-defined errors can be found in the Throwing Errors section of this guide.

For a list of valid HTTP status codes, see the List of HTTP status codes. Client libraries check these status codes to determine if a response contains an error. Make sure to follow the guidelines in the linked document.

If response.complete, response.continue, or response.error is not returned within the timeout period, your logic will be terminated and the client will be notified that processing timed out.

Business Logic Errors

Handling Errors

If an error occurs during execution of your Business Logic code, we return an HTTP status code and a JSON error response to the client.

The HTTP status codes returned are:

CodeDescription
400There was a runtime error in the Business Logic (probably triggered by a request) you should modify the request on the client and resubmit (or verify your code has no logic errors)
500There was an internal error within the Kinvey Business Logic system. Contact support@kinvey.com if this happens
550There was a server side error with your code, you should verify the correctness of your code

The format of the error response is:

{
    "debug": "",
    "description": "",
    "error": ""
}

Description is set to a descriptive message of the general failure, debug is set to the exact cause of the failure and error is one of:

ErrorCodeDescription
BLRuntimeError400There was a runtime error executing the JavaScript
BLSyntaxError550There was a syntax error compiling the JavaScript
BLTimeoutError500The backend processing failed to complete, please contact support@kinvey.com if you experience this error
BLViolationError550The Business Logic violated a constraint
BLInternalError550The Business Logic system experienced an internal error

Calling response.complete(400) or response.complete(550) will cause the client to see an error identical to normal Business Logic errors. Make sure to check the error in the response.

Validation and push triggers are built on Kinvey's Business Logic feature. You may see the above errors using the validation or push trigger features. If you're seeing a BLTimeoutError error, please contact support@kinvey.com.

Throwing Errors

At times, it may be necessary in your business logic code to throw an error back to the client. To accomplish this, your business logic code can return a User Defined Error to the client by calling response.error(err). This function either takes a String error message, or a JavaScript Error object or one of its subtypes. Errors are returned using the standard Kinvey error response format in the message body, with a response code of 400 (Bad Request). For example, the following code:

if (!myTestCondition) {
  return response.error("my custom message");
} else {
  return response.complete(200);
}

will produce a response.body of:

{
    "error": "BLRuntimeError",
    "description": "The Business Logic script has a runtime error. See debug message for details.",
    "debug": "UserDefinedRuntimeError:  my custom message"
}

If a JavaScript object such as a TypeError is passed, the response.body will reflect the JavaScript error type:

if (!myTestCondition) {
  var myError = new TypeError("Invalid Object Type");
  return response.error(myError);
} else {
  response.complete(200);
}
{
    "debug": "BLRuntimeError",
    "description": "The Business Logic script has a runtime error. See debug message for details.",
    "error": "TypeError:  Invalid Object Type"
}

Change notification

Kinvey has the ability to send email notifications to collaborators in an app whenever a BL script changes. To enable this functionality for your app, please contact Kinvey support.

Logging

The BL logger module offers a set of logging functions that can write log messages at different severity levels. The logger module is the only way to log in BL.

Viewing Logs

Use the Kinvey Console to view Business Logic logs.

Take these steps:

  1. In Kinvey Console, open the app environment that you are working in.
  2. In the left-hand side navigation, select the type of Business Logic whose logs you want to see: Collection Hooks or Scheduled Code.

    Common Code is always a part of Collection Hooks or Scheduled Code. To view messages generated by common code, go to the Collection Hook or Scheduled Code that uses it.

  3. In the list that appears, select the routine whose logs you want to view.

  4. Find the log messages in the lower pane.

    Using the pane controls, you can filter by severity level or search the messages. You can also irreversibly delete all logs. If you don't, older messages are automatically deleted when the maximum size limit is reached.

Retention Policy

The logger log entries are stored on the Kinvey servers and are subject to some restrictions, as described below.

  • The number of log messages is capped at 10,000 entries for all BL routines inside an app environment.
  • The cumulative size of all messages cannot exceed 5 MB for all BL routines inside an app environment.

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 logger and other output that is generated during the operation of the Node.js runtime, 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 and a callback. 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. The logger functions are used three times: to log an info-level message indicating each function execution, an error-level message containing the stringified error in case of failure, and another info-level message with the response body in case of success.

function deleteOldItems(request, response, modules) {
    modules.logger.info('Executing deleteOldItems Flex function');

    const deleteBefore = request.body.deleteBefore;
    query = {'_kmd.ect': {"$lte": deleteBefore}};

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

            modules.logger.info('Deletion succeeded: ' + JSON.stringify(response.body));
            return response.complete(200);
        }
    });
}

Testing Business Logic

After you complete a custom endpoint or a collection hook, you will want to test the code before sending it out to your live environment. Kinvey Console lets you do that using a Testing facility, which is a part of every custom endpoint or collection hook. If you want to test Common Code, test the custom endpoint or collection hook that includes it.

Testing Custom Endpoints

Take these steps:

  1. In Kinvey Console, open the app environment that you are working in.
  2. In the left-hand side navigation, select Business Logic > Custom Endpoints.
  3. In the list that appears, select the routine that you want to test.
  4. Click Testing above the code pane.

    A Test Settings sidebar appears.

  5. Complete the settings and then click Send Request.

SettingDescription
POST/PUTChoose the HTTP verb for the request.
AuthenticationSelect whether to run the request as a superuser (Master Secret), a specific app user (User Session or MIC, depending on the identity provider), or App Secret1.

1Only applicable to collection hooks.
Query StringRequest parameters that populate the request.params object. Use standard HTTP parameters encoding format: ?paramName1=value1&paramName2=value2&.... You can access each parameter using request.params.paramName` in your code.
Request BodyRequest body in JSON syntax that populates the request.body object. Ex: { "key1": "string1", "key2": 123 }. You can access each body value using request.body.key in your code.

Testing Collection Hooks

Take these steps:

  1. In Kinvey Console, open the app environment that you are working in.
  2. In the left-hand side navigation, select Business Logic > Collection Hooks.
  3. In the list that appears, select the routine that you want to test.
  4. Click Testing above the code pane.

    A Test Settings sidebar appears.

  5. Complete the settings and then click Send Request.

SettingDescription
AuthenticationSelect whether to run the request as a superuser (Master Secret), a specific app user (User Session or MIC, depending on the identity provider), or using the App Secret1.

1Only applicable when testing Pre- or PostSave collection hooks for the Users collection. Read more
Query StringRequest parameters that populate the request.params object. Use standard HTTP parameters encoding format: ?paramName1=value1&paramName2=value2&.... You can access each parameter using request.params.paramName` in your code.

Handling Business Logic NSError Errors

In the library you can check a NSError's code property against the following constants:

  • KCSBadRequestError for a 400
  • KCSServerErrorError for a 500
  • KCSBackendLogicError for a 550

The NSError's userInfo object will have the following keys for you to check the error details.

  • KCSErrorCode is the error value
  • KCSErrorInternalError is the debug value
  • NSLocalizedDescriptionKey is the description value

Examples

Business Logic is designed to help you perform tasks on the server that are either not possible or inefficient on a client. To use an example, just copy the code into the listed function for the collection that should trigger the logic.

Each example has a tip that tells you where in the management console to enter the code.

Business logic code (both Collection Hooks and Custom Endpoints) are managed in the Business Logic section of the console, under their respective folders. To create new hooks or endpoints, click the plus next to their respective folder names and follow the prompts.

For Collection Hooks, each of the listed events and request types (onPreSave, onPostFetch, etc) will only be triggered by a request of the same type (save, fetch, or delete) to the collection where the function is defined. Collections listed in the examples are other collections referenced in the logic don't need to be the same collection where the logic is defined.

Allow login with MIC only

In the login hook, there is a specific loginOptions.type property that holds information about the user login type. loginOptions.type has information about whether the login is with a Kinvey user (kinvey), MIC (kinveyAuth) or another provider - linkedIn for example.

In onPreLogin you may filter out users based on their credentials type, using theloginOptions.type property - whether it is a pure Kinvey user or a user that is trying to login with Mobile Identity Connect:

function onPreLogin(request, response, modules) {
  if (request.loginOptions.type === 'kinveyAuth') {
    // Allow login with MIC
    return response.continue();
  }

  // Not using MIC
  response.body = {
    error: 'KinveyUserLoginNotAllowed',
    description: 'You are trying to login using Kinvey credentials (username and passwrod). Only login with external identity provider is allowed.',
    debug: ''
  };
  response.complete(403);
}

Case-insensitive login on the server

Using the onPreLogin hook you may convert the username to lowercase letters only on one place on the server:

function onPreLogin(request, response, modules) {
  var lowerCaseUsername = request.body.username.toLowerCase();

  request.body.username = lowerCaseUsername;
  response.continue();
}

Notify on successful login

In onLoginSuccess, you may notify the user for successful login.

function onLoginSuccess(request, response, modules) {
  var from = 'my-app@app.com';
  var reply_to = from;
  var to = response.body.email;
  var subject = 'Successful login to my-app';
  var text_body = 'User ' + response.body.username + ' has successfuly logged-in in my-app';

  modules.email.send(from, to, subject, text_body, reply_to, function(err, result) {
    response.continue();
  });
}

Notify on login failure

In onLoginFailure, you may notify the user for login failed login attempts. You may also filter out when to notify the user - in the example below, the user will get an email notification only when he enters Invalid credentials.

function onLoginFailure(request, response, modules) {
  if (response.body.error == 'InvalidCredentials') {
    var from = 'my-app@app.com';
    var reply_to = from;
    var to = response.body.email;
    var subject = 'Unsuccessful login to my-app';
    var text_body = 'User ' + response.body.username + ' attempted to login in my-app but the login failed due to invalid credentials';

    modules.email.send(from, to, subject, text_body, reply_to, function (err, result) {
        response.continue();
    });
  }
}

Providing a test end-point

function onRequest(request, response, modules){
    response.body = {"someValue": "someOtherValue"};
    response.complete(201);
}

Any POST received by this endpoint just returns a 201 status with the response body of:

{
    "someValue": "someOtherValue"
}

Code is a Custom Endpoint Business Logic.

Requesting data from an API

function onRequest(request,response,modules){
    var req = modules.request;
    req.get('http://developer.mbta.com/Data/Red.json', function(error, resp, body){
        if (error){
            response.body = {error: error.message};
            response.complete(400);
            return;
        }
        response.body = JSON.parse(body);
        response.complete(resp.status);
    });
}

This requests the MBTA Red Line real-time subway feed, parses the JSON response and sends this JSON to your client.

APIs have various terms of service governing the use of the API. This includes rate limits, caching and storage policies, copyright guidelines and display requirements. Make sure to follow the API terms of service to avoid service interruptions. For example, the MBTA API ToS is documented in the MassDOT and Developer's Relationship Principles and the Developer's License Agreement documents.

Code is a Custom Endpoint Business Logic.

Posting data to an API

function onPreSave(request, response, modules){
  var endpointName = 'isCreditAvailable';  
  var uriString = 'https://baas.kinvey.com/rpc/' + 
    modules.backendContext.getAppKey() + '/custom/'+endpointName;
  var opts = {
    uri: uriString,
    method: 'post',
    headers: {
      'Authorization': request.headers.authorization
    },
    json:true,
    body: { "userid": request.body.userid, "balance": request.body.balance }
  };

  modules.request.request(opts, function( err, resp, body ) {
    if (err) {
      response.body = err;
    } else if (response.body.valid === true) {
      response.continue();
    } else {
        response.error("No balance remaining.");
       }
  });
}

This code sends a REST POST request using the request module to a custom endpoint for the purposes of validating if a particular account has credit available. Here, a request body in JSON format is provided, so json:true must be included in the request options. Authentication is passed through using the request.headers.authorization. Once the response is received, the request pipeline continues if there is balance available, and completes if there is not.

Code goes in the Before Save Business Logic code.

Triggering push notifications

function onPreSave(request,response,modules){
    var collectionAccess = modules.collectionAccess
    , userCollection = collectionAccess.collection('user')
    , utils = modules.utils
    , push = modules.push
    , template = '{{name}}, the {{team}} just won!'
    , pushedMessageCount = 0
    , userCount;


    // Find all users who's favoriteTeam matches
    // the incoming request's winning team.
    userCollection.find(
        {"favoriteTeam": request.body.winningTeam},
        function(err, userDocs){
        // Total number of messages to send
        userCount = userDocs.length;

        // Each message is customized
        userDocs.forEach(function(doc){
            var values = {
                name: doc.givenName,
                team: doc.favoriteTeam
            };

            // Render the message to send
            var message = utils.renderTemplate(template, values);

            // Send the push
            push.send(doc, message);

            // Keep track of how many pushes we've sent
            pushedMessageCount++;

            // reduce the number of users left to push to
            userCount--;
            if (userCount <= 0){
                // We've pushed to all users, complete the request
                response.body = {
                    "message": "Attempted to push " +
                    pushedMessageCount +
                    " messages."};
                response.complete(200);
            }
        });
    });
}

This sends a push notification to all users who match the winning team provided by a POST or PUT (also known as save in Kinvey client libraries) to a collection. Instead of saving any data to the data store, this request just causes the push notification to be sent.

The body of the request must have a winningTeam property, like:

{
    "winningTeam": "Boston Red Sox"
}

Users are expected to have a givenName property and a favoriteTeam property.

The Push notification will be similar to:

    Joe, the Kansas City Royals just won!

Code is a Custom Endpoint Business Logic.

Triggering email notifications

function onPostSave(request,response,modules){
    var i, to, logger = modules.logger, email = modules.email;
    for (i = 0; i < request.body.to.length; i++){
        to = request.body.to[i];
        logger.info("Sending e-mail to: " + to);
        email.send('my-app-invitation@my-app.com',
                   to,
                   request.body.subject,
                   request.body.body,
                   request.body.reply_to);
    }
    response.continue();
}

This sends an email to an array of email addresses on a POST or PUT (also known as save in Kinvey client libraries) to a collection. The body of the request should be similar to:

{
    "to": ["email1@example.com", "email2@example.com"],
    "subject": "This is a test email!",
    "body": "Please attend my event @ http://url.shorten/llayhshd",
    "reply_to": "event-creator@event-host.com"
}

Code goes in the After Save Business Logic code.

Merging data in two collections

function onPostFetch(request, response, modules){
    var collectionAccess = modules.collectionAccess
      , logger = modules.logger
      , body = request.body
      , resultA = null, resultB = null, callback;

    callback = function(){
        if (resultA && resultB){
            // Got both responses
            response.body = {things: resultA, stuff: resultB};
            response.complete(200);
        } else {
            // Still waiting for the other response.
            return;
        }
    };

    collectionAccess.collection('Things')
                    .find({name: body.thing}, function (err, docs)
    {
        if (err) {
            logger.error('Query failed: '+ err);
            response.body.debug = err;
            response.complete(500);
          } else {
            resultA = docs;
            callback();
          }
    });

    collectionAccess.collection('Stuff')
                    .find({name: body.stuff}, function (err, docs)
    {
        if (err) {
            logger.error('Query failed: '+ err);
            response.body.debug = err;
            response.complete(500);
          } else {
            resultB = docs;
            callback();
          }
    });

}

Give a request with a body of {"thing": "something", "stuff": "everybody's got stuff"}, this fetches the matching things and stuff from the datastore and then returns them to the client with a status code of 200.

Code goes in the After every Fetch Business Logic.

Denying access to a user

function onPreFetch(request, response, modules){
    var collectionAccess = modules.collectionAccess
      , logger = modules.logger
      , username = request.username;  // Set by Kinvey

    collectionAccess.collection('bannedUsers')
                    .find({user: username}, function (err, docs)
    {
          if (err) {
            logger.error('Query failed: '+ err);
            response.body.debug = err;
            response.complete(500);
        } else {
            if (docs.length > 0){
                // Banned user
                response.body.reason = docs[0].reason; // Just show the 1st reason
                response.complete(403);
            } else {
                // Not banned, let it through
                response.continue();
            }
        }
    });
}

This checks for the presence of the current user in the bannedUsers collection and rejects the request with a status code of 403 if the user is banned. It also reports the reason in the response body.

Code goes in the Before every Fetch Business Logic.

Returning the values deleted

function onPreDelete(request, response, modules){
    var collectionAccess = modules.collectionAccess
      , logger = modules.logger
      , query = request.params.query;

    collectionAccess.collection('thisCollection')
                    .find(query, function (err, docs)
    {
        if (err) {
            logger.error('Query failed: '+ err);
            response.body.debug = err;
            response.complete(500);
        } else {
            modules.utils.tempObjectStore.set('recordsDeleted', docs);
            response.continue();
        }
    });
}

function onPostDelete(request, response, modules){
    response.body = modules.utils.tempObjectStore.get('recordsDeleted');
    response.complete(200);
}

This runs the query that will be deleted against the datastore and stores the results in the tempObjectStore. The onPostDelete function extracts those and returns them to the client.

This has code for both the Before every Delete and After every Delete Business Logic code.

Returning a Kinvey Entity

When adding external data, whether generated through Business Logic or via an external API request, it is necessary to add Kinvey metadata to the object before submitting it to a Kinvey collection. The kinveyMetadata method can be used to return a skeleton object consisting of only Kinvey metadata, return a skeleton object with a user-assigned ID, or add/update Kinvey metadata on an existing JSON object. For example:

function onPreFetch(request,response,modules){
  var req = modules.request;
  var entity = modules.utils.kinveyEntity;
  req.get('http://api.openweathermap.org/data/2.5/weather?q=Boston,ma', 
      function(error, resp, body){
    if (error){
      response.body = {error: error.message};
      response.complete(400);
      return;
    }
    response.body = entity(JSON.parse(body));
    response.complete(resp.status);
  });
}

This example requests the current weather forecast for Boston, MA from OpenWeatherMap, parses the JSON response, and adds the Kinvey Metadata attributes before sending this JSON to your client.

APIs have various terms of service governing the use of the API. This includes rate limits, caching and storage policies, copyright guidelines and display requirements. Make sure to follow the API terms of service to avoid service interruptions. For example, the OpenWeatherMap ToS is documented in the How to work with the API Key section.

Code goes in the Before every Fetch Business Logic.

Manipulating Date/Time Values

To easily manipulate dates in Business Logic's JavaScript environment, use the module moment.

function onPostFetch(request, response, modules){
  var moment = modules.moment();
  var exp = response.body[0].trialExpiration;
  if (moment.add('days', 30).isBefore(exp)) {
    response.continue();
  } else if (moment.add('days',30).isAfter(exp) && moment.isBefore(exp)) {
      var body = response.body[0];
      body.warning = "Your trial will expire within the next 30 days."
      response.body[0] = body;
      response.continue(); 
  }else {
      response.error("Trial Period Expired.  Access Denied.");
  }
}

For more information on the moment module, see the reference

Code goes in the After every Fetch Business Logic.

Executing Asynchronous Functions

The async module utilizes async.js to simplify parallel and asynchronous programming within the business logic context. This module provides convenience methods for asynchronous functional methods (map, reduce, filter, each, etc) as well as some common patterns for asynchronous control flow (parallel, series, queue, etc…). For example:

function onPostFetch(request, response, modules){
  var async = modules.async;
    var body = response.body;
  var moment = modules.moment().format();
  var logger = modules.logger.error;

  var iterator = function(item, callback) {
    item.fetchTime = moment;
    callback();
  };

  var callback = function (err) {
    if (err) {
        logger(err);
    } else {
      response.body = body;
    }
    response.continue();
  };

  async.each(body, iterator, callback)
}

This request uses async.js after fetch of a collection or query of a collection to add a fetchTime timestamp to each record in the collection. By using the async.js each method, iterations through the response body are processed in parallel, improving response time and ability to update larger record sets in BL.

Code goes in the After every Fetch Business Logic.