Contents


Build and deploy an investment tracking application in the cloud with IBM Bluemix, Part 1

Connect and retrieve financial data using an open API

Comments

This tutorial was written using a previous version of the IBM Bluemix® interface. Given the rapid evolution of technology, some steps and illustrations may have changed.

In Part 1 of this two-part series, I show you how to connect and retrieve financial data using an open API. A mobile-compliant interface framework enables the application to work equally well on desktop computers and mobile devices. And hosting it on IBM Bluemix® ensures reliability and scalability. In Part 2, you'll see how to use this data in a PHP application to provide an instant, accurate valuation of an investment portfolio.

The sample application described in this series allows users to search for, and add, one or more stocks to their online investment portfolio.

What you'll need for your app

Note: Any application that uses the Google+ API must comply with the Google API's Terms of Service, the Google+ Platform Developer Policies, the Google+ Platform Terms of Service, and the Google Privacy Policies. Similarly, any application that uses the Quandl API must comply with Quandl's Terms of Use. Before beginning your project, spend a few minutes reading these requirements and ensuring that your application complies with them.

Run the sample appGet the code for the app

Download all the code and experiment!

You can download all the code implemented in this series from its Github repository, together with the configuration files for the PHP buildpack used here. I recommend that you get the code, start playing with it, and try adding new features to it. I guarantee you won't break anything, and it will definitely add to your learning. Have fun!

With the widespread availability of financial data through open APIs, it's easy to create a mobile web application that leverages that data to help users make sense of their financial lives.

Step 1. Create the application stub

  1. The first step is to initialize a basic application with the Silex PHP micro-framework. To do this, change to the web server's document root directory (typically, on Linux: /usr/local/apache/htdocs and on Windows: C:\Program Files\Apache\htdocs) and create a new directory for the application.
    shell> cd /usr/local/apache/htdocs
    shell> mkdir portfolio-tracker

    This directory is referenced throughout this article as $APP_ROOT. With this arrangement, your application should be directly accessible at the URL http://localhost/portfolio-tracker. Remember that once you deploy the application to Bluemix, its URL will change.

  2. Next, create the Composer configuration file, which should be saved to $APP_ROOT/composer.json:
    {
        "require": {
            "silex/silex": "*",
            "twig/twig": "*",        
            "hybridauth/hybridauth": "2.*",
            "guzzlehttp/guzzle": "*"
        },
        "minimum-stability": "dev",
        "prefer-stable": true
    }
  3. The Composer configuration file installs Silex, Twig, HybridAuth, and Guzzle, all of which are needed for building the application. Download and install these components using Composer as follows:
    shell> cd /usr/local/apache/htdocs/portfolio-tracker
    shell> php composer.phar install

Step 2. Construct the basic user interface

  1. The application interface should be as simple as possible, with two primary screens: one allowing the user to search for and add stocks, and another allowing the user to view the current value of each stock in the portfolio. The code below sets up a basic tabbed interface that conforms to this structure. Copy this code and save it as $APP_ROOT/views/index.twig.
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.css">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.js"></script>    
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
      </head>
      <body>
    
        <div data-role="page">
    
          <div data-role="header">
            <h1>Portfolio Tracker</h1>
          </div>
    
          <div data-role="content">
            <div data-role="tabs">
            
              <div id="navbar" data-role="navbar">
                <ul>
                  <li><a id="tab-search" href="#search" data-theme="a" class="ui-btn-active">Search</a></li>
                  <li><a id="tab-manage" href="#manage" data-theme="a" class="">Manage</a></li>
                </ul>
              </div>
              
              <div id="search">
                <!-- search tab -->
              </div>
    
              <div id="manage">
                <!-- manager tab -->          
              </div>
    
            </div>        
          </div>
    
          <div data-role="footer">
          </div>
    
        </div>
          
      </body>
    </html>
  2. This page sets up jQuery, jQuery Mobile, and AngularJS from the Google CDN, and defines a basic jQuery Mobile page. As you can see from the code above, the page currently contains a header, two tabs, and a footer. The tabs are currently empty; however, as we progress through this tutorial, you'll see them both rapidly fill up with content. On the server, Silex will take care of rendering this template when it encounters requests for URL routes such as / or /index. Here's the code, which should be saved to $APP_ROOT/index.php:
    <?php
    // use Composer autoloader
    require 'vendor/autoload.php';
    
    // load classes
    use Silex\Application;
    
    // initialize Silex application
    $app = new Application();
    
    // register Twig template provider
    $app->register(new Silex\Provider\TwigServiceProvider(), array(
      'twig.path' => __DIR__.'/views',
    ));
    
    // register URL generator
    $app->register(new Silex\Provider\UrlGeneratorServiceProvider());
    
    // index page handlers
    $app->get('/', function () use ($app) {
      return $app->redirect($app["url_generator"]->generate('index') . '#search');
    });
    
    $app->get('/index', function () use ($app) {
      return $app['twig']->render('index.twig');
    })
    ->bind('index');
    
    $app->run();

    The script above is the main control script for the application. It begins by loading all the required classes and initializing a new Silex application object. As part of the initialization process, the Twig template engine, template directory location, and URL generator service are also registered with the application.

    Silex works by defining router callbacks for HTTP methods and endpoints. This is done simply by calling the corresponding method—get() for GET requests, post() for POST requests, and so on—and passing the URL route to be matched as the first argument to the method. The second argument to the method is a function, which specifies the actions to be taken when the route is matched to an incoming request.

    The previous listing demonstrates this by setting up a router callback for the /index route and defining a function to be invoked when this route is matched to an incoming request. In the script above, the /index callback simply renders the main application page template shown above. The script also sets up a router callback for the / route, which simply redirects to the /index route. Notice the use of the Silex URL generator, which automatically generates the correct URLs for named routes after taking into account the current application path on the server.

    Silex depends on URL rewriting for friendly URLs, so this is a good time to configure your web server's URL rewriting rules. Because the application will eventually be deployed to an Apache web server, create a $APP_ROOT/.htaccess file and fill it with the following content:

    <IfModule mod_rewrite.c>
        Options -MultiViews
        
        RewriteEngine On
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule ^ index.php [QSA,L]
    </IfModule>
  3. Try to access your application at http://localhost/portfolio-tracker/index. You should see the jQuery Mobile page template that you set up earlier:Screen capture of jquery mobile template

Step 3. Add user authentication

The application is intended to support multiple users, each with its own portfolio. This implies a need for an authentication layer to ensure that users can't access each other's data, so that's the first thing we'll build.

HybridAuth is an open-source PHP library that lets you authenticate users against a wide variety of social networks and services, including Facebook, Google+, LinkedIn, Twitter, and many others. HybridAuth abstracts away the nitty-gritty of service authentication and authorization, making it easy to add social sign-on to a PHP application without worrying about the complexities of OAuth transactions and access tokens. In this application, I use HybridAuth to simplify user authentication by allowing users to authenticate themselves against Google+.

  1. HybridAuth uses OAuth for authentication, which means that you must register your web application with Google and give it access to the Google+ API for authentication. To do this, log in to Google using your Google Account credentials and visit the Google Developers Console. Create a new project, assign it a name, and then turn on access to the Google+ API in the project's "APIs" section. Screen capture showing google plus api
    Screen capture showing google plus api
  2. While you're there, obtain an OAuth 2.0 client id and secret for the application in the project's "Credentials" section. Make note of these values, as you will need them for HybridAuth.
  3. When obtaining the OAuth client ID and secret, also set the application redirect URL. This is the URL to which Google will redirect the client browser after completing the OAuth authentication process. For the moment, because you're developing locally, the URL should be set to your localhost domain, followed by the special path:
    /callback?hauth.done=Google,
    for example,
    http://localhost/portfolio-tracker/callback?hauth.done=Google.

    The special /callback route will be defined within the application. When the application is deployed to Bluemix, you will need to change the redirect URL to correctly reflect your Bluemix domain.

    Screen capture showing URL
    Screen capture showing URL
  4. With all the preliminaries dealt with, it's time to write some code. Begin by configuring HybridAuth in the application, by adding the lines below to your $APP_ROOT/index.php file:
    <?php
    // use Composer autoloader
    require 'vendor/autoload.php';
    require 'config.php';
    
    // load classes
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    use Silex\Application;
    
    // initialize Silex application
    $app = new Application();
    
    // load configuration from file
    $app->config = $config;
    
    // add configuration for HybridAuth
    $app->config['hybridauth']  = array(
      "base_url" => 'http://' . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['SCRIPT_NAME']), '/') . '/callback',
      "providers" => array (
      "Google" => array (
        "enabled" => true,
        "keys" => array (
          "id" => $app->config['oauth_id'],
          "secret" => $app->config['oauth_secret']
        ),
        "scope" => "https://www.googleapis.com/auth/userinfo.email"
    )));
    
    // register Twig template provider
    $app->register(new Silex\Provider\TwigServiceProvider(), array(
      'twig.path' => __DIR__.'/views',
    ));
    
    // register URL generator
    $app->register(new Silex\Provider\UrlGeneratorServiceProvider());
    
    // start session
    session_start();
    
    // initialize HybridAuth client
    $auth = new Hybrid_Auth($app->config['hybridauth']);
    
    // other handlers – snipped!
    
    $app->run();
  5. The code above initializes a new HybridAuth client, which is accessible via the $auth object. The OAuth client ID and secret were already generated for your application in the previous step; now you should add them to a configuration file, which serves as a convenient place to store application configuration information, including (later) the Quandl API key and database access credentials. Here's what the configuration file looks like:
    <?php
    // config.php
    $config = [
      'quandl_key'    => 'YOUR-QUANDL-API-KEY',
      'oauth_id'      => 'YOUR-OAUTH-ID',
      'oauth_secret'  => 'YOUR-OAUTH-SECRET',
      'db_uri'        => 'YOUR-CLOUDANT-DB-URI',
      'db_name'       => 'YOUR-DB-NAME'
    ];

    Update this file with the OAuth client ID and secret obtained from the Google Developers Console.
  6. Next, add some routes for logging in and out, together with the special /callback route that is needed for OAuth authentication:
    <?php
    
    // application initialization and other routes – snipped!
    
    // login handler
    // check if authenticated against provider
    // retrieve user email address and save to session
    $app->get('/login', function () use ($app, $auth) {
      $google = $auth->authenticate("Google");
      $currentUser = $google->getUserProfile();
      $_SESSION['uid'] = $currentUser->email;
      return $app->redirect($app["url_generator"]->generate('index') . '#search');
    })
    ->bind('login');
    
    // logout handler
    // log out and display logout information page
    $app->get('/logout', function () use ($app, $auth) {
      $auth->logoutAllProviders();
      session_destroy();
      return $app['twig']->render('logout.twig');
    })
    ->before($authenticate);
    
    // OAuth callback handler
    $app->get('/callback', function () {
      return Hybrid_Endpoint::process();
    });
    
    $app->run();

This code adds three new route handlers:

  • The /login route handler uses the previously initialized HybridAuth client to authenticate the user against the Google+ API via the client's authenticate() method. If authenticated, the user's basic profile information, including his or her email address, is retrieved from the Google+ API and stored in a session variable named $_SESSION['uid']. This session variable is used to identify a user's portfolio when saving or viewing records in the database.
  • The /logout route handler uses the HybridAuth client's logoutAllProviders() method to disconnect from all OAuth providers and log the user out of the application. However, it is important to note that logging out from the application still leaves the Google OAuth session active. The /logout callback therefore also renders a logout page, which offers the user an additional option to log out of the connected Google account and completely expire the OAuth token and session. You can see this logout page template in the source code repository for the application, at $APP_ROOT/views/logout.twig.
  • The /callback route handler is used to handle redirection from the Google OAuth processor. The Hybrid_Endpoint::process() method abstracts away the work of handling requests to this endpoint.

Because the $_SESSION['uid'] variable will exist only if a user is successfully authenticated, it can be used to protect access to the other application routes, as shown below:

<?php
// register authentication middleware
$authenticate = function (Request $request, Application $app) use ($config) {
  if (!isset($_SESSION['uid'])) {
    return $app->redirect($app["url_generator"]->generate('login'));
  }    
};

The $authenticate function above is Silex "middleware": It is automatically run before a request is processed by attaching it to the corresponding route handler using the before() method. This makes it possible to restrict access and allow only authenticated users to request those application routes. It works by checking for the presence of the $_SESSION['uid'] variable in the session. If it's not present, it redirects the user to the /login route, forcing a re-login.

Once a user has successfully logged in, his or her email address will be available in the $_SESSION['uid'] variable. It then becomes possible to use this variable in page templates. So, for example, if you wanted to display the user's email address in the main application interface, you could simply pass this variable to the index page template, as shown in this modified version of the /index route handler:

<?php

// application initialization and other routes – snipped!

$app->get('/index', function () use ($app) {
  $uid = $_SESSION['uid'];
  return $app['twig']->render('index.twig', array('uid' => $uid));
})
->before($authenticate)
->bind('index');

Note the before() method that's newly attached to the route, which runs the $authenticate middleware described previously. This ensures that the index page is visible only to users after authentication.

Here's the revised index page template, which now includes an additional sub-header displaying the logged-in user's email address and a separate Sign out button pointing to the /logout route.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.js"></script>    
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
  </head>
  <body>

    <div data-role="page">

      <div data-role="header">
        <h1>Portfolio Tracker</h1>
        <a data-ajax="false" href="{{ app.request.basepath }}/logout" data-role="button" class="ui-btn-right">Sign out</a>
        <div class="ui-bar ui-bar-a" style="text-align: center">
          {{ uid }}
        </div>
      </div>

      <div data-role="content">
        <div data-role="tabs">
        
          <div id="navbar" data-role="navbar">
            <ul>
              <li><a id="tab-search" href="#search" data-theme="a" class="ui-btn-active">Search</a></li>
              <li><a id="tab-manage" href="#manage" data-theme="a" class="">Manage</a></li>
            </ul>
          </div>
          
          <div id="search">
            <!-- search tab -->
          </div>

          <div id="manage">
            <!-- manager tab -->          
          </div>

        </div>        
      </div>

      <div data-role="footer">
      </div>

    </div>
      
  </body>
</html>

To see how this works, try browsing to the application index page at http://localhost/portfolio-tracker/index. You should be prompted to log in to your Google account and, once you've done that, you will be asked whether to grant the application access to your profile information.

Screen capture showing Google login prompt
Screen capture showing Google login prompt

Once you agree, the OAuth process will complete and you will be redirected to the application index page, which should now display your account email address and offer you a Sign out button in the header bar. Here's what it looks like:

Screen capture showing sign out button
Screen capture showing sign out button

Step 4. Understand the Quandl API

The next step is to set up the application's search interface, which is largely driven off of the Quandl API.

The Quandl API is free to use and offers access to both open and premium financial databases. It responds to HTTP requests with JSON responses containing the requested data. The data can then be parsed and used both by server-side programming languages (such as PHP and Perl) and client-side tools (such as jQuery and AngularJS).

The Quandl API supports thousands of datasets, but for the purposes of this article, you need only one: the free WIKI dataset, which is a community-curated list of end-of-day stock prices and dividends for 3,000 US companies. Although you can access this dataset for free, you're limited to fewer than 50 calls per day without an API key; that number increases to 50,000 calls per day once you register and get an API key.

Assuming that you've already signed up for a Quandl API account and have a valid API key, try taking the API for a test drive by retrieving price information for IBM. Simply point your browser to https://www.quandl.com/api/v3/datasets/WIKI/IBM.json?api_key=[YOUR-API-KEY], remembering to replace the API key placeholder with your actual API key. You should see a response like this:

Screen capture showing results of search for IBM
Screen capture showing results of search for IBM

As the image illustrates, the Quandl API responds to the request with a JSON document listing the company name, code, unique identifier, and price details. Price information is organized in a data array, with each element of the array representing a single trading day. For each day, the information provided includes the opening price, high, low, closing price, trading volume, ex-dividend price, and more.

Of course, this is just one dataset. Quandl provides many more such datasets, some free and some commercial. For example, if you're interested in the Indian stock market, you can use the free NSE dataset. Or, if you're interested in tracking US interest rates, you can use the free US Federal Reserve dataset.

You can also search a specific dataset for items matching a search term. For example, if you wanted to search for all stocks matching the keyword "inc", you could request the URL https://www.quandl.com/api/v2/datasets.json?api_key=[YOUR-API-KEY]&source_code=WIKI&query=inc. This would query the WIKI dataset and return a result of matching stocks, as shown below:

Screen capture showing results of search for stocks with inc in name
Screen capture showing results of search for stocks with inc in name

Step 5. Enable the search interface

  1. Now that you know how the API works, it's time to get started building the application. The first step is to update the basic page template with a search form, so that the user can begin searching for matching stocks. Update the $APP_ROOT/views/index.twig page so that it looks like this:
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.css">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.js"></script>    
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
      </head>
      <body>
    
        <div data-role="page">
    
          <div data-role="header">
            <h1>Portfolio Tracker</h1>
            <a data-ajax="false" href="{{ app.request.basepath }}/logout" data-role="button" class="ui-btn-right">Sign out</a>
            <div class="ui-bar ui-bar-a" style="text-align: center">
              {{ uid }}
            </div>
          </div>
    
          <div data-role="content">
            <div data-role="tabs">
            
              <div id="navbar" data-role="navbar">
                <ul>
                  <li><a id="tab-search" href="#search" data-theme="a" class="ui-btn-active">Search</a></li>
                  <li><a id="tab-manage" href="#manage" data-theme="a" class="">Manage</a></li>
                </ul>
              </div>
              
              <div id="search" ng-app="myApp" ng-controller="myAppController">
                <h2 class="ui-bar ui-bar-a">Stock Search</h2>
                <div class="ui-body">
                    <input type="search" name="query" ng-model="items.query" />
                    <button ng-click="search()">Search</button>
                </div>      
                <h2 class="ui-bar ui-bar-a">Search Results</h2>   
                <div class="ui-body">
                  <ul data-role="listview" data-split-theme="d">
                    <li ng-repeat="r in items.results">
                    {% verbatim %}
                      <a>{{r.name}}</a>
                    {% endverbatim %}
                      <a href="{{ app.request.basepath }}{% verbatim %}/add/{{r.code}}{% endverbatim %}" data-ajax="false" data-inline="true" data-role="button" data-icon="plus" data-theme="a">Add</a>                
                    </li>
                  </ul>                    
                </div>          
              </div>
    
              <div id="manage">
              </div>
    
            </div>        
          </div>
    
          <div data-role="footer">
          </div>
    
        </div>
          
      </body>
    </html>

    Notice that the search tab is now linked to an AngularJS controller named myAppController, and the search input field uses an AngularJS model named items. Clicking the Search button invokes an AngularJS method called search(), whose job it is to run an AJAX query that will eventually return a list of stocks matching the user's query.

    The ng-repeat directive in the above template takes care of formatting those stocks into a jQuery Mobile list view, complete with an Add button next to each. The Add button is linked to the /add route and includes the stock symbol in the URL. Because both Twig and AngularJS use {{...}} symbols for variable interpolation, I've also used Twig's {% verbatim %} tag to explicitly identify the AngularJS placeholders.

  2. That's the template. Now, here's the AngularJS controller, which completes the picture. This code should be added to $APP_ROOT/views/index.twig before the closing </head> element.
     <script>      
      var myApp = angular.module('myApp', []);
      
      myApp.controller("myAppController", function ($scope, $http) {
        $scope.items = {};
        $scope.items.results = [];
        $scope.items.query = '';
        
        $scope.search = function() {
          if ($scope.items.query != '') {
            $http({
                method: 'GET',
                url: '{{ app.request.basepath }}/search/' + $scope.items.query,
              }).
              success(function(data) {
                $scope.items.results = data.docs;
              });
          } else {
            $scope.items.results = [];
          }
        };
      });
    </script>

If you're familiar with AngularJS, you'll quickly realize that there's nothing very complicated about this. When the user clicks the Search button, the search() controller method executes an AJAX query to the /search endpoint and populates the items model with the results. As described previously, the ng-repeat directive takes care of formatting and presenting these results using a jQuery Mobile list.

It's important to note that the /search endpoint is not the Quandl API endpoint, but rather an intermediary API endpoint that's managed by the application itself. This endpoint is defined in the $APP_ROOT/index.php file as a Silex callback, as shown below:

<?php
// use Composer autoloader
require 'vendor/autoload.php';
require 'config.php';

// load classes
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Silex\Application;

// initialize application – snipped!

// initialize HTTP client
$guzzle = new GuzzleHttp\Client();

$app->get('/search/{query}', function ($query) use ($app, $guzzle) {
  // execute search on Quandl API
  // specify search scope and required response fields
  $response = $guzzle->get('https://www.quandl.com/api/v2/datasets.json?api_key=' . $app->config['quandl_key'] . '&source_code=WIKI&query='.urlencode($query));
  $result = $response->getBody();
  // remove unwanted trailing strings
  $result = str_replace(' Prices, Dividends, Splits and Trading Volume', '', $result);
  if ($result) {
    return new Response($result, Response::HTTP_OK, array('content-type' => 'application/json'));
  } else {
    return new Response(null, Response::HTTP_NOT_FOUND);  
  }
})
->before($authenticate);

$app->run();

The /search callback handles AJAX search requests sent by the AngularJS controller. It accepts a search term, then uses the Guzzle HTTP client to construct a request URL to the Quandl API, as shown in Step 4. The response is returned to the application front end as a JSON document after some basic tidying up. As discussed previously, AngularJS then takes care of binding the response data to the scope.

It's worth pointing out that you could also execute the AJAX request directly from the application front end using AngularJS. However, doing this would expose your private Quandl API application key to end users, which is not recommended for a publicly accessible application. Having a server-side script perform the request instead adds some overhead, but offers greater security.

  1. Once you've added in all the code, try browsing to your application again. This time, you should see a search form and, when you enter a search term into the query field, you should see a list of matching search results. Each search result will include an Add button, linked to the /add route and containing the unique stock symbol corresponding to the result.Screen capture showing stock search form
    Screen capture showing stock search form

Conclusion

You've now seen how to initialize the basic application and user interface, enable authentication using the Google+ API, and connect it with the Quandl API. In Part 2, you'll see how to store details of an investment portfolio and use the data retrieved via the Quandl API to generate real-time valuations.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Cloud computing, Mobile development, Web development
ArticleID=1022709
ArticleTitle=Build and deploy an investment tracking application in the cloud with IBM Bluemix, Part 1
publish-date=12022015