Build and deploy a REST API on IBM Bluemix with PHP and MySQL

26 June 2014
PDF (1021 KB)
 

Build and deploy a REST API on IBM Bluemix with PHP and MySQL

05:20  |  Transcript
author photo - Vikram Vaswani

Vikram Vaswani

Founder and CEO of Melonfire

vikram-vaswani.in

Introduction

 

Sign up for IBM Bluemix
This cloud platform is stocked with free services, runtimes, and infrastructure to help you quickly build and deploy your next mobile or web application.

If you're building an application that interacts with an online product or service, chances are that you're using a REST API to pull data out or push data in. REST APIs have become increasingly popular over the last few years because they're easy to understand, can be coded quickly, and are usable across all programming languages via built-in HTTP support.

If you're developing your own cloud service, a REST API is also a great way to promote data sharing and reuse. By allowing external developers to access your data via REST, you make it easy for them to build cool new applications and come up with interesting ways to piggyback on your product or data. Think about the application ecosystem that's grown around Facebook, Twitter, or Instagram, and the benefits should be clear.

"By allowing external developers to access your data via REST, you make it easy for them to build cool new applications and come up with interesting ways to piggyback on your product or data."

In this article, I give you a crash course in creating a REST API with Bullet, a PHP micro-framework. In addition to explaining how to implement the four basic REST methods, I also show you how to add support for common features, such as API authentication and multiformat support, and then deploy the API to IBM Bluemix™. So come on in, and let's get started!

Understand REST API basics

 

READ:Wikipedia page on REST

To begin, let's spend a few minutes understanding REST, otherwise known as Representational State Transfer. REST is a style of API development based on "resources" and "actions." A resource is simply a URL referencing the object or entity on which you want to perform the actions — for example, /users or /photos — and an action is one of the four HTTP verbs:

  • GET (retrieve)
  • POST (create)
  • PUT (update)
  • DELETE (remove)

An example makes this clearer. Assume that you have an application designed for tracking software bugs and you want to enable easy reuse and manipulation of the data in the application database. You'd expose a URL endpoint — let's call it /bugs — and then allow external developers to access this endpoint using different HTTP methods and content (for example, GET /bugs to list all bugs or DELETE /bugs/78 to delete bug #78).

Based on the HTTP method and content, it's possible to deduce the operation being requested and take appropriate action on the data. Here are some examples:

  • GET /documents: Retrieve a list of documents.
  • GET /bugs/123: Retrieve bug #123.
  • POST /photos: Create a new photo with the POST request body.
  • PUT /photos/123: Update photo #123 with the PUT request body.
  • DELETE /orders/123: Delete order #123.

What you need

 

The example REST API I develop in this article assumes a database of products and focuses on allowing you to retrieve, add, delete, and update these products using normal REST conventions. To keep things simple, the Product resource only has three attributes: a unique identifier, a name, and a price. I further assume that product data is stored in a MySQL database and that API requests and responses are encoded in JSON (although in a later section, I also discuss using XML as an alternative).

Here's what you need:

Step 1: Set up the application database

 

Use the following MySQL table definition and sample data to set up the application database.

  • If you're developing and deploying only locally, you can use this to initialize a MySQL database table for the API to connect to.
  • If you're deploying on Bluemix, skip this step for the moment; we come back to it in Step 7, once you've initialized and bound a MySQL service instance on Bluemix.
CREATE TABLE IF NOT EXISTS `products` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `price` decimal(5,2) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

INSERT INTO `products` (`id`, `name`, `price`) VALUES
(1, 'Garden spade', 15.99),
(2, 'Cotton hammock', 54.50),
(3, 'Single airbed', 35.49);

Step 2: Install Bullet and Eloquent

 

The next step is to download and set up the Bullet micro-framework. Why Bullet? It includes a flexible URL router that makes it easy to create and respond to different HTTP methods on custom URLs, plus nested routing callbacks to simplify the execution of common repetitive tasks (such as API authentication).

To make database access easier, I'm going to use Eloquent, a popular Object Relational Mapper (ORM) that uses an ActiveRecord implementation to make working with database records easier. Eloquent is part of the Laravel PHP framework, but it can also be used on a stand-alone basis.

I'll use Composer, the PHP dependency manager, to download and set up Bullet and Eloquent. Here's the Composer configuration file, which you should save to $APP_ROOT/composer.json. Throughout this article, $APP_ROOT refers to the application working directory.

{
    "require": {
        "vlucas/bulletphp": "*",
        "illuminate/database": "*"
    }
}

You can now install Bullet and Eloquent using Composer with the command:

shell> php composer.phar install

To make it easier to access the application, you can also define a new virtual host in your development environment and point its document root to $APP_ROOT. This step, although optional, is recommended because it creates a closer replica of the target deployment environment on Bluemix.

  • To set up a named virtual host for the application in Apache, open the Apache configuration file (httpd.conf or httpd-vhosts.conf) and add these lines to it.
	NameVirtualHost 127.0.0.1
	<VirtualHost 127.0.0.1>
	    DocumentRoot "/usr/local/apache/htdocs/api"
	    ServerName api.localhost
	</VirtualHost>
  • To set up a named virtual host for the application in nginx, open the nginx configuration file (nginx.conf) and add these lines to it.
	server {
	    server_name api.localhost;
	     root /usr/local/apache/htdocs/api;
	     try_files $uri /index.php;
	     
	     location ~ \.php$ {
	        try_files $uri =404;            
	        include fastcgi_params;
	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        # assumes you are using php-fcgi
        fastcgi_pass 127.0.0.1:90;
	    }        
	}

These lines define a new virtual host, http://api.localhost/, whose document root corresponds to the $APP_ROOT (remember to update it to reflect your own local settings). Restart the web server to activate these new settings. Note that you might need to update your network's local DNS server to tell it about the new host.

Step 3: Handle GET requests for resource collections

 

Bullet works by parsing URL segments one at a time and responding to specific path or parameter patterns using callbacks. Callbacks can be defined on a per HTTP method basis, allowing you to perform different actions for GET requests versus POST requests, for example.

The typical REST API supports two types of GET requests: the first for a collection of resources (GET /products) and the second for a specific resource (GET /products/123). Let's begin by writing code to handle the first scenario: responding to a GET /products request with a list of all available products from the database. Update your $APP_ROOT/index.php file with the following code.

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';


// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // GET /v1/products
    // list all products
    $app->get(function() use ($app)  {
      $products = Product::all();
      return $products->toArray();
    });

  });
    
});

echo $app->run(new Bullet\Request());

The first step is to initialize the Composer auto-loader, which takes care of loading required classes as needed. Then, create a new Bullet\App object, representing the application, and use the application object's path() method to set up a nested URL route for the endpoint /v1/products. The innermost path() callback defines the supported methods for that endpoint — in this case, using the application object's get() method to define a handler for GET requests.

The route handler uses an Eloquent model to return all products from the database. Because the handler returns an array, Bullet automatically assumes the response format should be JSON and takes care of converting the array and sending it back to the client with a 'Content-Type: application/json' header and a 200 OK success code. Errors, if any, are trapped by the exception handler and returned to the client with a 500 Internal Server Error code and a JSON body containing the error message.

The application object's run() method, shown in the last line of the previous listing, brings it all together by executing the Bullet application when an incoming request is received.

At this point, you're probably thinking it all makes sense, except for one question: Where does the Eloquent model come from? The next listing makes it clearer, by adding the code necessary to use Eloquent with the GET handler shown above.

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// use Eloquent ORM
use Illuminate\Database\Capsule\Manager as Capsule;  
use Illuminate\Database\Schema\Blueprint as Schema;  
 
// create model for Eloquent ORM mapped to REST API resource
class Product extends Illuminate\Database\Eloquent\Model {
  public $timestamps = false;
}

// get MySQL service configuration from Bluemix
$services = getenv("VCAP_SERVICES");
$services_json = json_decode($services, true);
$mysql_config = $services_json["mysql-5.5"][0]["credentials"];
$db = $mysql_config["name"];
$host = $mysql_config["host"];
$port = $mysql_config["port"];
$username = $mysql_config["user"];
$password = $mysql_config["password"];

// initialize Eloquent ORM
$capsule = new Capsule;
 
$capsule->addConnection(array(
  'driver'    => 'mysql',
  'host'      => $host,
  'port'      => $port,
  'database'  => $db,
  'username'  => $username,
  'password'  => $password,
  'charset'   => 'utf8',
  'collation' => 'utf8_unicode_ci',
  'prefix'    => ''
));

$capsule->setAsGlobal();
$capsule->bootEloquent();


// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // GET /v1/products
    // list all products
    $app->get(function() use ($app)  {
      $products = Product::all();
      return $products->toArray();
    });

  });
    
});

echo $app->run(new Bullet\Request());

Eloquent comes with a Capsule manager instance, designed to make it easy to use Eloquent outside Laravel. The previous listing demonstrates this Capsule instance in action, setting up a new connection to the MySQL database and making the Capsule instance available globally.

Because the intent of this article is to eventually deploy the application to Bluemix, the credentials for the MySQL connection are extracted from the special Bluemix VCAP_SERVICES environment variable; however, if you plan to deploy locally, you can simply replace these with values specific to your local database server.

You'll also notice a Product model that extends the Illuminate\Database\Eloquent\Model class. This Product model provides a number of predefined methods, which makes it easier to work with the corresponding database records. You've already seen one of them —Product::all()— in action, which internally takes care of generating a SELECT query to "return all products in the database."

Here's the result of a successful GET request to /v1/products.

A GET request to /v1/products

Step 4: Handle POST requests for new resources

 

READ:HTTP Response Status Codes

In a similar manner, it's possible to handle POST requests and convert them into new product records.

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // POST /v1/products
    // create new product
    $app->post(function($request) use ($app) {
      $product = new Product();
      $product->name = trim(htmlentities($request->name));
      $product->price = round(trim(htmlentities($request->price)), 2);
      if ($product->name && $product->price) {
        $product->save();
        return $app->response(201, $product->toArray());
      } else {
        return 400;
      }

    });

  });
    
});

echo $app->run(new Bullet\Request());

This listing adds a new handler for POST requests to /v1/products. When the application receives a POST request, it creates an empty Product object, sets the Product 'name' and 'price' from the request object, and calls the model's save() object to add the new record to the database.

If the record is successfully added, the handler returns a 201 Created status code and a JSON representation of the new Product resource. If not, it returns either a 400 Bad Request or a 500 Internal Server Error error code and a JSON message body explaining the error. See examples of a successful and unsuccessful POST request and response below.

A successful POST request to /v1/productsAn unsuccessful POST request to /v1/products

One interesting point worth mentioning: The new Product resource to be created is passed to the API as a JSON object in the POST body content. However, within the POST handler, you'll see that there's no usage of PHP's json_decode() function to decode the POST payload. This is because Bullet automatically does this decoding for you when it encounters a JSON payload in a POST or PUT request. You can then use JSON object properties directly, as properties of the request object.

Step 5: Handle GET, PUT and DELETE requests for individual resources

 

READ:HTTP Response Status Codes

In a previous section, I explained how to handle GET requests for a resource collection (GET /v1/products). But a REST API should also permit addressing resources directly, using their unique resource identifier (GET /v1/products/123 or DELETE /v1/products/123). This makes it possible to retrieve, update, or delete individual resources using the API.

Just like the path() method you saw earlier, designed for static URL paths, Bullet also includes a param() method, which is designed to handle URL paths containing variables. The following listing demonstrates this in the context of a GET request handler for a specific resource.

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
      
    $app->param('int', function($request, $id) use($app) {

      $product = Product::find($id);
      if(!$product) {
        return 404;
      }
           
      // GET /v1/products/:id
      // list single product by id
      $app->get(function($request) use($product, $app) {
        return $product->toArray();
      });
           
      
    });        

  });
    
});

echo $app->run(new Bullet\Request());

This listing sets up a callback handler for URLs of the form /v1/products/[id], where [id] is a variable path segment representing the unique resource identifier. The 'int' argument further specifies that this variable is an integer, assigned to the $id variable.

In this listing, the resource identifier captured from the URL route is passed to the Product model, and the model's find() method is used to return the corresponding database record. Bullet then takes care of transforming the record into JSON and returning it to the client. If no matching records are found, the handler returns a 404 Not Found error code. Errors, if any, are trapped by the exception handler and passed back with a 500 Internal Server Error error code.

Here is the result of a successful GET request and response.

A GET request to /v1/products/:id

With this structure in place, it's quite simple to add support for DELETE and PUT requests for a specific resource as well. Take a look at the following listing, which includes this additional functionality.

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
      
    $app->param('int', function($request, $id) use($app) {

      $product = Product::find($id);
      if(!$product) {
        return 404;
      }
                
      // GET /v1/products/:id
      // list single product by id
      $app->get(function($request) use($product, $app) {
        return $product->toArray();
      });

      // PUT /v1/products/:id
      // update product by id
      $app->put(function($request) use($product, $app) {
        $product->name = trim(htmlentities($request->name));
        $product->price = round(trim(htmlentities($request->price)), 2);
        if ($product->name && $product->price) {
          $product->save();
          return $app->response(200, $product->toArray());
        } else {
          return 400;          
        }
      });
          
      // DELETE /v1/products/:id
      // delete product by id
      $app->delete(function($request) use($product) {
        $product->delete();
        return 204;
      });
      
    });        

  });
    
});

echo $app->run(new Bullet\Request());
  • A PUT request signifies an update to an existing resource, with the updated information in the PUT request body. The listing defines a PUT handler, which reads the JSON document in the PUT request and updates the properties of the Product object retrieved using Eloquent. The updated resource is then saved back to the database using the object's save() method. A 200 Accepted server response code is returned to the client to indicate that the operation was successful, together with a representation of the updated resource.
  • A DELETE request implies that the resource referenced in the DELETE URL should be removed from the data store, and the DELETE handler above does just that: it calls the retrieved Product object's delete() method to remove the product record from the MySQL database. In this case, success is signified with a 204 No Content response code and an empty response body.

The following images display successful PUT and DELETE requests.

A PUT request to /v1/products/:idA DELETE request to /v1/products/:id

An interesting point to note here is that the three nested route handlers above all use the $product instance created in the parent handler. By defining common routines or objects once in a parent handler and then using them in all child closures or blocks, there are fewer lines of code being duplicated, making maintenance and updates easier.

Step 6: Support multiple formats

 

Most of the time, you'll be fine with JSON request and response bodies. But what if you also want to support another format, like XML? With Bullet, you can define custom responders for different formats, so you can easily handle this requirement.

To illustrate, consider the next listing, which adds support for XML output to the /v1/products GET endpoint:

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// function to convert array to XML
function convert_array_to_xml($data) {
  $xml = new SimpleXMLElement('<root/>');
  foreach ($data as $r) {
    $item = $xml->addChild('product');
    $item->addChild('id', $r['id']);
    $item->addChild('name', $r['name']);
    $item->addChild('price', $r['price']);
  }
  return $xml->asXml();
}

// initialize application
$app = new Bullet\App();

$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // GET /v1/products[.xml|.json]
    // list all products
    $app->get(function() use ($app)  {

      $products = Product::all();         
      
      // handle requests for XML content
      $app->format('xml', function($request) use($app, $products) {
        return $app->response(200, convert_array_to_xml($products->toArray()))
                      ->header('Content-Type', 'application/xml');
      });
        
      // handle requests for JSON content
      $app->format('json', function($request) use($app, $products) {
        return $products->toArray();
      });  
            
    });
        
  });    
  
});

echo $app->run(new Bullet\Request());

The application object's format() method is used to define separate callback handlers for XML and JSON output. When Bullet encounters a request for the URL /v1/products.xml or a request for the URL /v1/products with an 'Accept: application/xml' header, it automatically invokes the XML callback handler. In the latter case, Bullet uses automatic content negotiation to understand the requested format and deliver the output accordingly.

As you can see from the previous listing, this handler simply converts the array of product information retrieved from the database into an XML document and returns it to the client. The following image displays the output.

A GET request to /v1/products.xml

To obtain output in JSON format, the client should instead request the URL /v1/products.json or the URL /v1/products with an 'Accept: application/json' header, as shown in the output below.

A GET request to /v1/products.json

In a similar manner, it's possible to update the POST handler so that it can accept input in XML format as well as JSON format.

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// function to convert array to XML
function convert_array_to_xml($data) {
  $xml = new SimpleXMLElement('<root/>');
  foreach ($data as $r) {
    $item = $xml->addChild('product');
    $item->addChild('id', $r['id']);
    $item->addChild('name', $r['name']);
    $item->addChild('price', $r['price']);
  }
  return $xml->asXml();
}

// initialize application
$app = new Bullet\App();

$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
  
    // POST /v1/products[.xml|.json]
    // create new product
    $app->post(function($request) use ($app) {

      // handle requests for XML content
      $app->format('xml', function($request) use($app) {
        $input = simplexml_load_string($request->raw());
        $product = new Product();
        $product->name = trim(htmlentities((string)$input->name));
        $product->price = round(trim(htmlentities((string)$input->price)), 2);
        if ($product->name && $product->price) {
          $product->save();
          return $app->response(201, convert_array_to_xml(array($product->toArray())))
                    ->header('Content-Type', 'application/xml');          
        } else {
          return 400;
        }
      });
        
      // handle requests for JSON content
      $app->format('json', function($request) use($app) {          
        $product = new Product();
        $product->name = trim(htmlentities($request->name));
        $product->price = round(trim(htmlentities($request->price)), 2);
        if ($product->name && $product->price) {
          $product->save();
          return $app->response(201, $product->toArray());
        } else {
          return 400;
        }
      });   

    });   
        
  });    
  
});

echo $app->run(new Bullet\Request());

Here, an XML-formatted POST body sent to /v1/products.xml is converted into a PHP object using the simplexml_load_string() method, and the properties of this object are used to populate a new Product instance. Once saved, an XML representation of the newly-minted Product resource is returned to the client.

The following image displays an example of the XML POST request and response.

A POST request to /v1/products.xml

If a client wants to use JSON instead, it can POST a JSON request body to the URL /v1/products.json. In this case, Bullet automatically decodes the JSON request body into an object, which can be turned into a Product instance and saved to the database. The client receives a JSON representation of the saved Product resource, as shown below. As discussed previously, the same result can also be achieved by sending an 'Accept: application/json' header as part of the POST request to the URL /v1/products.

A POST request to /v1/products.json

Step 7: Handle errors and add authentication

 

BulletPHP comes with a powerful event handling system that can be used to perform custom actions based on a specific server code, response format or exception type. To illustrate, consider the next listing, which uses an event handler to automatically capture exceptions and relay them to the client as a JSON-encoded packet.

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize application
$app = new Bullet\App();

// global 'Exception' handler event
$app->on('Exception', function($request, $response, Exception $e) use ($app) {
  // send 500 error with JSON info about exception
  $response->status(500);
  $response->content(array(
    'exception' => get_class($e),
    'message' => $e->getMessage()
  ));
});

// route handlers
// snipped

echo $app->run(new Bullet\Request());

Here is an example of how the API would respond to an exception.

API exception handling

It's also possible to use this event handling system to perform actions 'before' or 'after' a request is processed, or on custom events. A common use for this is authentication: you can trigger a custom 'authentication required' event for protected routes, then write a handler for that event to check if a requesting client has the credentials necessary to access those routes.

This next listing demonstrates with a simple example. A custom 'auth' event is triggered on protected routes; this transfers control to an event handler which checks authentication credentials (in this case, plaintext cookies) before processing the request further.

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize application
$app = new Bullet\App();

// runs when 'auth' event is triggered on protected API routes to check that
// credentials (here, plaintext cookies) are present
// replace with more complex authentication function in production
$app->on('auth', function($request, $response) use ($app) {

  if (!$request->cookie('uid') == 'demo' || !$request->cookie('pass') == 'demo') {
    $response->status(401);
    $response->send();
    exit;
  }
  
});

// unprotected API route to set authentication cookies
$app->path('set-auth-cookies', function($request) use ($app) {

  $app->get(function() use ($app)  {
    setcookie('uid', 'demo');
    setcookie('pass', 'demo');
    return 200;
  });
  
});

// protected API route
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
  
    // trigger authentication event
    $app->filter('auth');

    // route handler code
    // snipped
    }
}

echo $app->run(new Bullet\Request());

In this example scenario, a user can create the cookies by requesting the special /set-auth-cookies route; in a production environment, the user might need to sign up for an API key and provide it with every request.

The image below shows how the API would respond to an unauthenticated request with a 401 Unauthorized code.

API response to an unauthenticated request

The demo version of the API linked at the top of this article can be used without authentication. To enable authentication, download the source code for the API from Devops Services, uncomment the relevant section and deploy a new instance.

Step 8: Deploy to Bluemix

 

READ:Deploy a Hello World PHP Application to Zend Server on Bluemix

Now that you've got the API all coded, the final step is to deploy it.

  • If you're deploying locally, you're all done. Skip to the end of this section, as it lists some useful tools you can use to interact with the API.
  • If you're deploying on Bluemix, you need a Bluemix account and you also need to download and install the Cloud Foundry command-line client. Follow the steps below to complete the deployment process.

a. Create your application manifest

 

READ:Deploy a minimal Node.js application to Bluemix

The application manifest file tells Bluemix how to deploy your application. In particular, it specifies the PHP run-time environment ("build pack") that should be used. Create this file at $APP_ROOT/manifest.yml, filling it with the information below.

---
applications:
- name: products-api-[random-number]
memory: 256M
instances: 1
host: products-api-[random-number]
buildpack: https://github.com/dmikusa-pivotal/cf-php-build-pack.git

Remember to update the host and application name to make it unique, either by changing it or by appending a random number to it. I'm using the Cloud Foundry PHP build pack, although other alternatives are also available.

b. Set up URL routing for Bullet

 

READ:Configure URL Rewriting for Framework-Based PHP Applications on IBM Bluemix

By default, the Cloud Foundry PHP build pack uses Apache as its Web server. Nginx is a lighter alternative, so, in this step, we need to override the build pack's default settings to use nginx instead as the Web server. Before starting, remember that you can obtain all the files in this section from the project's DevOps Services source code repository.

First, create a $APP_ROOT/.bp-config directory and then create $APP_ROOT/.bp-config/options.json with the following content.

{
    "WEB_SERVER": "nginx"
}

At this point, it's also necessary to set URL rewriting rules for nginx, so that it correctly passes API routes to Bullet's URL router. First, create $APP_ROOT/.bp-config/nginx/server-defaults.conf with the following content.

        listen @{VCAP_APP_PORT};
        server_name _;

        fastcgi_temp_path @{TMPDIR}/nginx_fastcgi 1 2;
        client_body_temp_path @{TMPDIR}/nginx_client_body 1 2;
        proxy_temp_path @{TMPDIR}/nginx_proxy 1 2;

        real_ip_header x-forwarded-for;
        set_real_ip_from 10.0.0.0/8;
        real_ip_recursive on;

        try_files $uri /index.php;

Then, create $APP_ROOT/.bp-config/nginx/server-locations.conf with the following content.

        # Some basic cache-control for static files to be sent to the browser
        location ~* \.(?:ico|css|js|gif|jpeg|jpg|png)$ {
            expires max;
            add_header Pragma public;
            add_header Cache-Control "public, must-revalidate, proxy-revalidate";
        }

        # Deny hidden files (.htaccess, .htpasswd, .DS_Store).
        location ~ /\. {
            deny all;
            access_log off;
            log_not_found off;
        }

        # pass .php files to fastcgi
        location ~ .*\.php$ {
            try_files $uri =404;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_pass php_fpm;
        }

c. Connect to Bluemix and deploy the application

 

Use the 'cf' command line tool to log in to Bluemix using your IBM ID and password.

shell> cf api https://api.mybluemix.net
shell> cf login

Change to the $APP_ROOT directory and push the application to Bluemix.

shell> cf push

Here's a sample of what you see during this process.

Deploying an application to Bluemix

At this point, your application should be deployed to Bluemix. But you're not done yet!

d. Bind a MySQL service instance to the application

 

Your application is now deployed, but you still need to connect it with a MySQL database instance so that your API has some data to work with. To do this, visit the Bluemix administration dashboard and log in with your IBM id and password. You should see your application listed in the 'Apps' menu bar.

Bluemix applications dashboard

Select your application and on the resulting page, use the 'Add new service' option to add the 'mysql' service to your application.

Binding MySQL service to application on Bluemix

You should now be able to see a MySQL service instance bound to your application in the Bluemix administration dashboard.

MySQL service instance bound to Bluemix application

In particular, if you inspect your application details, you should also be able to see the MySQL access credentials in the VCAP_SERVICES environment variable.

Bluemix application environment variables

e. Install the example schema

 

At this point, you can go ahead and initialize the application database, using the example schema shown in Step 1. Although there's no way to directly run the SQL commands in Step 1 in the Bluemix environment, the application includes a special route named /install-schema, which you can simply request through your browser to set up the database table and sample data. Although this route is not documented in this article, you can find it in the source code repository for the application.

The image below illustrates the result of a successful schema installation via the /install-schema route.

6. Start using your REST API
Schema installation on Bluemix

Once your REST API is deployed, you can start sending GET, POST, PUT, and DELETE requests to it.

  • Firefox's HttpRequester and Chrome's Postman extensions are powerful tools to construct and send different types of API requests through your browser. Most of the screenshots in this article have been generated using the Postman extension.
  • You can use any popular client- or server-side programming language to submit requests to the API: jQuery, PHP, Perl, Java, and Python (among others) all support HTTP request submission and response processing.
  • This article also includes a live demo of the API using Swagger, which provides a browser-based tool for API exploration. You can use this demo to add and update products, delete products or retrieve individual products. The image below shows the Swagger front-end in action.Swagger API explorer

Conclusion

 

As this article has illustrated, Bluemix provides a solid foundation for creating and deploying REST APIs on a cloud-based platform. Add Bullet's flexible support for common REST conventions and Eloquent's highly-intuitive ORM, and you've got everything you need to quickly prototype and deploy your own custom REST API.

You can download all the code implemented in this article from its Devops Services repository, together with the configuration files for the PHP buildpack used in this article. 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.

Add a comment

Note: HTML elements are not supported within comments.


1000 characters left

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development, Cloud computing
ArticleID=970847
ArticleTitle=Build and deploy a REST API on IBM Bluemix with PHP and MySQL
publish-date=06262014