Skip to content

Service Development

Service implementation will consist of four classes:

  • The API service implementation is in the ShoppingCartService class.
  • There are two data model classes: ShoppingItem and Order, they are used to represent data objects stored in Backendless Data Service
  • The ShoppingCart class is used by the service implementation to store any intermediate state between the service calls.
  • Create the /app/models directory in the root directory of your project. The directory will contain classes defining your data model.
  • Create shopping-item.js in the /app/models/ directory with the following contents:

'use strict';

/**
 * @property {String} objectId
 * @property {String} product
 * @property {Number} price
 * @property {Number} quantity
 */
class ShoppingItem extends Backendless.ServerCode.PersistenceItem {

}

module.exports = Backendless.ServerCode.addType(ShoppingItem);

Notice the following details about the code above:* The class extends Backendless.ServerCode.PersistentItem. Since instances of the ShoppingItem class are stored in the Backendless data storage, this inheritance adds for convenience several instance methods like save, remove, loadRelations, .., and static class methods like find, findLast, findById, ... * The Backendless.ServerCode.addType method registers the class as one of the types used during server code invocation. That means that for any service method which uses the class as an argument, CodeRunner™ will be able to adapt/cast/convert incoming argument objects into instance of the ShoppingItem class. For added convenience, the addType method returns the passed object. * The ShoppingItem class is added to module.exports. This is done to enable access to this class from other modules. * The annotation in the comment for the method is very important. It is processed by Backendless and is used for service API generation. 1. Create order.js in the /app/models/ directory with the following contents:

'use strict';

class Order extends Backendless.ServerCode.PersistenceItem {
  constructor(items) {
    super();

    /**
     * @type {ShoppingItem[]}
     */
    this.items = items || [];

    /**
     * @type {Number}
     */
    this.orderPrice = this.items.reduce((sum, item) => {
      return (sum || 0) + (item.price * item.quantity);
    }, 0);
  }
}

module.exports = Backendless.ServerCode.addType(Order);
1. Create shopping-cart.js in the /app/models/ directory with the following contents:

'use strict';

class ShoppingCart {
  constructor(opts) {
    opts = opts || {};

    this.name = opts.name;
    this.items = opts.items || [];
    this.___class = ShoppingCart.name;
  }

  addItem(item) {
    item.objectId = null;

    this.items.push(item);
  }

  deleteItem(product) {
    const idx = this.items.findIndex(item => item.product === product);

    if (idx === -1) {
      throw new Error(`No ${product} in cart`);
    }

    this.items.splice(idx, 1);

    return this;
  }

  setQuantity(product, quantity) {
    const productItem = this.items.find(item => item.product === product);

    if (!productItem) {
      throw new Error(`No [${product}] in cart`);
    }

    productItem.quantity = quantity;

    return this;
  }

  getItems() {
    return this.items;
  }

  destroy() {
    return Backendless.Cache.remove(this.name, this);
  }

  save() {
    return Backendless.Cache.put(this.name, this);
  }

  static get(name, mustExist) {
    Backendless.Cache.setObjectFactory(ShoppingCart.name, ShoppingCart);

    return Backendless.Cache.get(name).then(cart => {
      if (!cart && mustExist) {
        throw new Error(`Shopping cart [${name}] does not exist`);
      }

      return cart;
    });
  }
}

module.exports = ShoppingCart;
1. Create the /app/services directory in the project's root directory. 1. Create shopping-cart-service.js in the /app/services/ directory with the following contents:

'use strict';

const Order        = require('../models/order'),
      ShoppingCart = require('../models/shopping-cart');

class ShoppingCartService {

  /**
   * @param {String} cartName
   * @param {ShoppingItem} item
   * @returns {Promise.<void>}
   */
  addItem(cartName, item) {
    return this.addItems(cartName, [item]);
  }

  /**
   * @param {String} cartName
   * @param {Array.<ShoppingItem>} items
   * @returns {Promise.<void>}
   */
  addItems(cartName, items) {
    return ShoppingCart.get(cartName).then(cart => {
      if (!cart) {
        cart = new ShoppingCart({ name: cartName });
      }

      items.forEach(item => cart.addItem(item));

      return cart.save();
    });
  }

  /**
   * @param {String} cartName
   * @param {String} product
   * @returns {Promise.<void>}
   */
  deleteItem(cartName, product) {
    return ShoppingCart.get(cartName, true).then(cart => cart.deleteItem(product).save());
  }

  /**
   * @param {String} cartName
   * @returns {Promise.<Array.<ShoppingItem>>}
   */
  getItems(cartName) {
    return ShoppingCart.get(cartName, true).then(cart => cart.getItems());
  }

  /**
   * @param {String} cartName
   * @param {String} productName
   * @param {Number} quantity
   * @returns {Promise.<void>}
   */
  setQuantity(cartName, productName, quantity) {
    return ShoppingCart.get(cartName, true).then(cart => cart.setQuantity(productName, quantity).save());
  }

  /**
   * @param {String} cartName
   * @returns {Promise.<Order>}
   */
  purchase(cartName) {
    return ShoppingCart.get(cartName, true).then(cart => {
      const order = new Order(cart.getItems());

      return order.save()
        .then(() => cart.destroy())
        .then(() => order);
    });
  }
}

Backendless.ServerCode.addService(ShoppingCartService);

Notice the following details about the code above:

  • The service class is registered with the Backendless.ServerCode.addService method.
  • All methods of a class registered as service become individual API endpoints when the service is deployed into CodeRunner™.
  • The annotations in the comments are used by Backendless to create service API declaration.

When you are done with this part of the guide, your project's directory structure should look as in the screenshot below:

js-service-structure