Developing with REST

The application’s released, the userbase is growing, and the company inbox is filling up with inquiries from developers curious about your data set. Maybe it’s time to start thinking about opening the API.

It’s usually a good idea to approach development with an API-first mindset, but many older applications were designed primarily to serve themselves. Even well-established applications can benefit from opening parts of their platform, however, a move that can:

  • Expand the use of the application’s dataset
  • Increase relevance of the application’s platform
  • Enable growth through third party features and enhancments

REST provides one roadmap for exposing application data to the world. Before diving into the details, it’s worth mentioning that there are many easier ways to develop a RESTful API without building it from scratch. But for existing applications whose architecture doesn’t fit the conventions of an existing framework, a custom implementation may represent a lesser evil.

To keep the REST-zealots at bay, the journey we’re about to embark upon won’t outline a fully RESTful development approach. Call it REST-ish, if you like—80% there, with the details omitted for the sake of brevity.

A brief introduction to REST

REST is an architectural style that envisions the internet as a collection of server-based resources made available to clients at unique locations (URLs). Clients make requests to access or modify individual resources based on the resource’s location and the action to be taken. For instance, a RESTful request to access a single blog post might look something like this:

GET /blog/posts/14

If it looks a lot like an HTTP GET request, that’s because it is: in REST, an HTTP request defines the “verb” used to operate on the resource located at a specific URL. Mapped over the usual CRUD, REST typically relies on the following mappings:

Request TypeResourceCollection
GETReadRead allPOSTCreatePUTUpdateReplace collectionDELETEDeleteDelete
Sticking with the blog example, a GET request for `/blog/posts/14` would be interpreted by the server as a request to return the blog post with ID #14. Similarly, a GET request for ‘/blog/posts’ would return an index of all blog posts.

Beyond the verb and the resource location, the format of the request matters only in that the server must recognize and respond in the same format. In theory, a full-on implementation of REST should be able to meet any request with a response in the same tongue. In reality, “RESTful” applications rarely implement more than one or two serialization formats (XML and JSON being the common cases).

Handling Requests

In PHP, most of the details of a RESTful request end up in the $_SERVER array. The specific fields may vary slightly depending on how the server is set up—in applications that rely on the front controller pattern, for instance, the URL of the requested resource is likely to be stored in $_SERVER['QUERY_STRING'] instead—but those relevant to request processing typically include:

Request`$_SERVER` fieldNotes
Resource`REQUEST_URI`See belowType`REQUEST_METHOD`Format`CONTENT_TYPE`PUT and POST only
The body of the request itself will only be set for PUT and POST requests, but may be retrieved from PHP's input buffer:
$method = $_SERVER[ 'REQUEST_METHOD' ];

if( $method == 'POST' || $method == 'PUT' ) {
    $request = file_get_contents( 'php://input' );
}

The $request retrieved from the input buffer is just a string containing a serialized representation of the PUSHed or POSTed resource. Fortunately, the mime-type specified by $_SERVER['CONTENT_TYPE'] offers some clue over to the format that was used to encode it—information that a listening service can use to translate the request string into a native PHP object.

$method = $_SERVER[ 'REQUEST_METHOD' ];

if( $method == 'POST' || $method == 'PUT' ) {

    $request = file_get_contents( 'php://input' );
    $format = $_SERVER[ 'CONTENT_TYPE' ];

    switch( $format ) {
    case 'application/json':
        // decode JSON request
        $request = json_decode( $request );
    break;
    default:
        // unknown format
    }
}

From here, request processing should pass to some kind of handler function. There are a several ways to do this, and the “right” approach will depend on the style of the application in question. One common pattern, however, passes execution to a handler class with get(), post(), update(), and delete() methods mapped as callbacks to the appropriate REST methods.

Sending responses

Once a request has been received and translated, the application will need to generate an appropriate response. A RESTful response has three parts:

// 1. A status code describing the success or failure of the request
header( 'HTTP/1.0 200 OK' );

// 2. A content-header affirming the format of the response
header( 'content-type: application/json' );

// 3. The (appropriately encoded) body of the response
echo json_encode( $response );
exit();

REST relies on HTTP status codes to determine whether a request was successful. Though it probably makes better sense to create methods for each status result, an index of statuses can illustrate the point:

$code = 200;

$descriptions = array(
    200 => 'OK',
    400 => 'bad request',
    404 => 'not found',
    500 => 'internal server error',
    // ...
);

header( "HTTP/1.0 $code {$descriptions[$code]}" );

The content-type is easy—nine times out of ten, it should match the $format of the request—and the response body is just a matter of ensuring that the resulting resource is serialized appropriately. The same mime-type switch previously employed to decode the request format can now be used to convert a native object ($response) into the appropriate format.

switch( $format ) {
case 'application/json':
    // encode response
    $response = json_encode( $response );
break;
}

Putting it all together, a generic response method might look something like this:

public function response( $code, $format, $response ) {

    $descriptions = array(
        200 => 'OK',
        400 => 'bad request',
        404 => 'not found',
        500 => 'internal server error',
        // ...
    );

    header( "HTTP/1.0 $code {$descriptions[$code]}" );
    header( 'content-type: ' . $format );

    switch( $format ) {
    case 'application/json':
        $response = json_encode( $response );
    break;
    }

    echo $response;
    exit();
}

Conclusion

REST is just one architecture among many that a development team might select to expose an application, but its close relationship with HTTP and simple structure make it a particularly common one. A full-blown API will be considerably more involved than the coarse interactions outlined here, of course, but it will also likely take advantage of one of the many excellent REST implementations available for the development team’s language of choice. Check ‘em out!

Featured