Chapter 8. REST

REST stands for REpresentational State Transfer, and in contrast to protocols such as SOAP or XML-RPC, it is more a philosophy or a set of principles than a protocol in its own right. REST is a set of ideas about how data can be transferred elegantly, and although it’s not tied to HTTP, it is discussed here in the context of HTTP. REST takes great advantage of the features of HTTP, so the earlier chapters covering this and the more detailed topics of headers and verbs can all come together to support a good knowledge of REST.

In a RESTful service, four HTTP verbs are used to provide a basic set of CRUD (Create, Read, Update, Delete) functionality: POST, GET, PUT, and DELETE. It is also possible to see implementations of other verbs in RESTful services, such as PATCH to allow partial update of a record, but the basic four provide the platform of a RESTful service.

The operations are applied to resources in a system. The “Representational State Transfer” name is accurate; RESTful services deal in transferring representations of resources. A representation might be JSON or XML, or indeed anything else. So what is a resource? Well, everything is. Each individual data record in a system is a resource. At the first stage of API design, a starting point could be to consider each database row as an individual resource. Think of an imaginary blogging system as an example: resources might be posts, categories, and authors. Every resource has a URI, which is the unique identifier for the record.

A collection contains multiple resources (of the same type); usually this is a list of resources or the result of a search operation. A blog example might have a collection of posts, and another collection of posts limited to a particular category.

RESTful URLs

RESTful services are often thought of as “pretty URL” services, but there’s more than prettiness to the structures used here. In Chapter 5, the GitHub API was used as an example of an API using JSON; it is also a nice example of a RESTful API belonging to a system that developers may already be familiar with. Take a look at some of the URLs in this API:

These delightful, descriptive URLs allow users to guess what will be found when visiting them, and to easily navigate around a predictable and clearly designed system. They describe what data will be found there, and what to expect. A key characteristic of RESTful URLs is that they only contain information about the resource or collection data—there are no verbs in these URLs. The best of API designs will have URLs that are “hackable”—that is to say that they are predictable enough to successfully guess where to find things. This links closely to the idea of hypermedia, which we’ll discuss shortly.

In order to alter how a collection is viewed (for example, to add filtering or sorting to it), it is common to add query parameters to the URL, like so:

Notice that the URLs are not along the lines of /events/sortBy/Past or any other format that puts extra variables in the URL, but they use query variables instead. This data set, in both cases, still utilizes the /events/ collection, but sorted and/or filtered accordingly.

Resource Structure and Hypermedia

Exactly how the resource is returned can vary hugely; REST doesn’t dictate how to structure the representations sent. For example, a GitHub gist in JSON format looks like this:

{
  "url": "https://api.github.com/gists/17018bf64b89dd179322",
  "forks_url": "https://api.github.com/gists/17018bf64b89dd179322/forks",
  "commits_url": "https://api.github.com/gists/17018bf64b89dd179322/commits",
  "id": "17018bf64b89dd179322",
  "git_pull_url": "https://gist.github.com/17018bf64b89dd179322.git",
  "git_push_url": "https://gist.github.com/17018bf64b89dd179322.git",
  "html_url": "https://gist.github.com/17018bf64b89dd179322",
  "files": {
    "text.txt": {
      "filename": "text.txt",
      "type": "text/plain",
      "language": "Text",
      "raw_url": "https://gist.githubusercontent.com/lornajane/17018bf64b89dd179322/raw/336516c8e23e55265245bf589ae56aafa9cbbcf2/text.txt",
      "size": 18,
      "truncated": false,
      "content": "Some riveting text"
    }
  },
  "public": true,
  "created_at": "2015-07-23T18:30:57Z",
  "updated_at": "2015-07-23T18:30:57Z",
  "description": "Gist created by API",
  "comments": 0,
  "user": null,
  "comments_url": "https://api.github.com/gists/17018bf64b89dd179322/comments",
  "owner": {
    "login": "lornajane",
    "id": 172607,
    "avatar_url": "https://avatars.githubusercontent.com/u/172607?v=3",
    "gravatar_id": "",
    "url": "https://api.github.com/users/lornajane",
    "html_url": "https://github.com/lornajane",
    "followers_url": "https://api.github.com/users/lornajane/followers",
    "following_url": "https://api.github.com/users/lornajane/
    following{/other_user}",
    "gists_url": "https://api.github.com/users/lornajane/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/lornajane/
    starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/lornajane/subscriptions",
    "organizations_url": "https://api.github.com/users/lornajane/orgs",
    "repos_url": "https://api.github.com/users/lornajane/repos",
    "events_url": "https://api.github.com/users/lornajane/events{/privacy}",
    "received_events_url": "https://api.github.com/users/lornajane/received_events",
    "type": "User",
    "site_admin": false
  },
  "forks": [

  ],
  "history": [
    {
      "user": {
        "login": "lornajane",
        "id": 172607,
        "avatar_url": "https://avatars.githubusercontent.com/u/172607?v=3",
        "gravatar_id": "",
        "url": "https://api.github.com/users/lornajane",
        "html_url": "https://github.com/lornajane",
        "followers_url": "https://api.github.com/users/lornajane/followers",
        "following_url": "https://api.github.com/users/lornajane/
        following{/other_user}",
        "gists_url": "https://api.github.com/users/lornajane/gists{/gist_id}",
        "starred_url": "https://api.github.com/users/lornajane/
        starred{/owner}{/repo}",
        "subscriptions_url": "https://api.github.com/users/lornajane/
        subscriptions",
        "organizations_url": "https://api.github.com/users/lornajane/orgs",
        "repos_url": "https://api.github.com/users/lornajane/repos",
        "events_url": "https://api.github.com/users/lornajane/events{/privacy}",
        "received_events_url": "https://api.github.com/users/lornajane/received_events",
        "type": "User",
        "site_admin": false
      },
      "version": "0392fec1b0a32463ec005942fb088aae6c47a763",
      "committed_at": "2015-07-23T18:30:57Z",
      "change_status": {
        "total": 1,
        "additions": 1,
        "deletions": 0
      },
      "url": "https://api.github.com/gists/17018bf64b89dd179322/0392fec1b0a32463ec005942fb088aae6c47a763"
    }
  ]
}

Whereas a talk from Joind.in, also in JSON, would look like this:

{
    "talks": [
        {
            "talk_title": "Everything You Ever Wanted to Know About Deployment But Were Afraid to Ask",
            "talk_description": "Deployment can be a real bugbear for many web developers. From building something easy to deploy and manage; to coming up with a repeatable, consistent process; to continuous deployment...deployment can keep you up at night for months on end. In this talk I’ll cover the following topics:
- The deployment maturity model
- How to build a deployable application, from technology choice to instrumentation
- Deployment velocity: Why your process matters more than how often you deploy
- Deployment tools and processes: How to automate your troubles away
- CI/Automated testing: Know you’re deploying something good, or at least how worried you should be about it
- Automated testing vs monitoring: How they converge
- When are you ready to deploy continuously? How do you make the jump?",
            "start_date": "2012-11-08T13:00:00-05:00",
            "average_rating": 5,
            "comments_enabled": 1,
            "comment_count": 4,
            "speakers": [
                {
                    "speaker_name": "Laura Thomson",
                    "speaker_uri": "http://api.joind.in/v2.1/users/20041"
                }
            ],
            "tracks": [],
            "uri": "http://api.joind.in/v2.1/talks/7660",
            "verbose_uri": "http://api.joind.in/v2.1/talks/7660?verbose=yes",
            "website_uri": "http://joind.in/talk/view/7660",
            "comments_uri": "http://api.joind.in/v2.1/talks/7660/comments",
            "verbose_comments_uri": "http://api.joind.in/v2.1/talks/7660/comments?verbose=yes",
            "event_uri": "http://api.joind.in/v2.1/events/1056"
        }
    ],
    "meta": {
        "count": 1,
        "this_page": "http://api.joind.in/v2.1/talks/7660?start=0&resultsperpage=20"
    }
}

The two formats are quite different, and in fact the fields and formats available in a RESTful service will differ between each and every kind of service you could wish to encounter. But there are some common features, as can be seen even from this small sample size. Both responses include some nested information and some links out to other resources or collections. The decisions you make around handling data formats and versioning your API are a topic in their own right and covered in Chapter 12.

Build the Basic RESTful Server

REST makes the most of HTTP’s best features, placing all the metadata about the request and response into the headers, and reserving the main body of the communications for the actual content. This means that a correctly implemented RESTful service will make use of verbs, status codes, and headers so that all the extra information goes in the “envelope” of the request, and only the content is in the body. See Appendix A and Appendix B for tables of common status codes and headers.

Example Project: The Wishlist

Since it’s always easier to be shown than told, I’ve created a very tiny example RESTful service to use as an example, less than a hundred lines of PHP code in a single file (and a separate storage class that you can find on the GitHub repo)! We’ll use this to show the concepts in very plain PHP and see a simple service in action, then move on and discuss the options for real-world RESTful implementations in PHP.

This particular example just allows us to add items to a wishlist, giving them a name and linking to where that product can be found online. As I mentioned, there’s a very simple storage class that just writes to CSV behind the scenes, so if you want to pull the code from this book’s accompanying repository and try it for yourself, it should work regardless of your plaform. The basic structure of the wishlist example project is shown in Example 8-1.

Example 8-1. Basic structure of my one-file RESTful service
<?php

require("ItemStorage.php");

set_exception_handler(function ($e) {
  $code = $e->getCode() ?: 400;
  header("Content-Type: application/json", NULL, $code);
  echo json_encode(["error" => $e->getMessage()]);
  exit;
});

// assume JSON, handle requests by verb and path
$verb = $_SERVER['REQUEST_METHOD'];
$url_pieces = explode('/', $_SERVER['PATH_INFO']);
$storage = new ItemStorage();

// catch this here, we don't support many routes yet
if($url_pieces[1] != 'items') {
  throw new Exception('Unknown endpoint', 404);
}

switch($verb) {
  case 'GET':
    ...
    break;
  // two cases so similar we'll just share code
  case 'POST':
  case 'PUT':
    ...
    break;
  case 'DELETE':
    ...
  default:
    throw new Exception('Method Not Supported', 405);
}

// this is the output handler
header("Content-Type: application/json");
echo json_encode($data);

Looking at the structure, there are a few elements there that are quite important in RESTful services and that are worth a mention. First, there’s the ItemStorage class that just hides all the CSV and array manipulation so that we can concentrate on the RESTful elements.

The exception handler is declared nice and early and importantly, it is output-format aware. It is very frustrating for users when a service that should usually return JSON or XML suddenly returns HTML when an error occurs. Much better practice is to return in the expected data format even when there is an error. This example service just assumes you want JSON, but in Chapter 3 we discussed content negotiation and for a service that supports multiple response formats, it is best to parse those headers and work out what that preferred output format is as soon as possible so that the exception handler can return it if anything goes wrong.

In a RESTful service, the action that is performed by the service is dependent hugely on the verb in use so that’s parsed out. It will also be necessary to know which URL was actually accessed, and usually we inspect the individual pieces of a URL so that is parsed here. Beware that depending on your web server and rewrite rule setup, you may need to use $_SERVER[REQUEST_URI] rather than $_SERVER[PATH_INFO]. There’s also a little check here since our application will only support requests to /items, so anything else we just throw the Exception and let the exception handler return a sensible format. Notice that this includes the correct status code to return—it is entirely not acceptable to return a 200 when things did not go well! Status codes are important in REST and you’ll see them used with all our examples as we work through them.

The only other thing to mention before we look at the individual verbs is the two lines at the end of the file. Technically this is the “output handler,” where our application will translate the return data to the correct format (recursively if required) and return it with the correct headers. For this case where we’re assuming JSON, it’s pretty straightforward, as you can see. Making sure that all output goes through common handlers is a great way of ensuring consistent formats and also making sure that elements such as metadata, hypermedia, and other touches are correct everywhere. This makes maintaining your API, and potentially adding a new data format in the future, much less painful.

There are some mysterious blank patches in this example code that we’ll move on to inspect by visiting each verb in turn.

Create Resources with POST

Resources are created by making a POST request to the collection to which the new resource will belong. The body of the request will contain a representation of the new resource, with the Content-Type header set appropriately so that the server will know how to understand it. When the resource has been successfully created, a successful status code will be included with the response.

It’s common to choose a status code of 201 (which means “Created”) when a new resource has been made, and to either return a representation of the new resource in the body, or to set a Location header, redirecting the consumer to the URI of the new record. It’s also perfectly valid to return either a 202 “Accepted” (but not completed) status code or a 200 “OK”—and it’s also helpful to return a representation of the resource (appropriately formatted according to the Accept header) including information about the URI of this new item.

In the event that the resource cannot be created, an informative status code and error message should be returned to the user. In general, a 400 “Bad Request” status code would be appropriate for a request that either wasn’t understood, or didn’t pass validation rules. If a response can’t be served in a format understood by the client, then 406 “Not Acceptable” would be appropriate to indicate a content negotiation problem. There are also a very large number of other status codes to choose from (see Appendix A for a handy list), depending on what exactly went wrong.

In the wishlist example application, the code for a request made with the POST verb is so similar to the code needed for PUT that they are combined. The result is shown in Example 8-2.

Example 8-2. Example code for creating and updating resources in the example wishlist application
  case 'POST':
  case 'PUT':
    // read the JSON
    $params = json_decode(file_get_contents("php://input"), true);
    if(!$params) {
      throw new Exception("Data missing or invalid");
    }
    if($verb == 'PUT') {
      $id = $url_pieces[2];
      $item = $storage->update($id, $params);
      $status = 204;
    } else {
      $item = $storage->create($params);
      $status = 201;
    }
    $storage->save();

    // send header, avoid output handler
    header("Location: " . $item['url'], null,$status);
    exit;
    break;

The code snippet here first reads the php://input stream, which is the raw body of the request as it came in. We use this when working with JSON since the $_POST functionality only works on form data. First, the incoming data is put through json_decode() and then checked—if the data is missing or if the JSON wasn’t valid then this will return false and we throw an Exception.

To keep the code short, there’s a lack of validation before we pass the data through to the ItemStorage class to create a new record and then to save it. The correct response when creating a record successfully is to return a 201 “Created” status and offer a redirect in the shape of a Location header to point to where the newly created resource can be found.

If I call this with cURL on the command line, you can see the request and response in Example 8-3 and observe all of this in action.

Example 8-3. Create a new resource on the RESTful wishlist service using cURL
$ curl -v -X POST -H "Content-Type: application/json" http://localhost:8080/rest.php/items --data '{"link": "http://www.amazon.co.uk/dp/B00FWRIAUS/","name": "notebook"}'
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /rest.php/items HTTP/1.1
> User-Agent: curl/7.38.0
> Host: localhost:8080
> Accept: */*
> Content-Type: application/json
> Content-Length: 69
>
* upload completely sent off: 69 out of 69 bytes
< HTTP/1.1 201 Created
< Host: localhost:8080
< Connection: close
< X-Powered-By: PHP/5.6.4-4ubuntu6
< Location: http://localhost:8080/rest.php/items/dd44d
< Content-type: text/html; charset=UTF-8
<
* Closing connection 0

First, look at the request I made. It’s a POST request and I also included the Content-Type header since I’m sending JSON in the body. This example API assumes JSON, but it is good practice to check that the expected data format arrives, or even to accept more than one format, in which case you will need to read the headers to make sure you know how to interpret the body.

My command also includes the -v switch to cURL, which makes it show the headers in the request and response in addition to the response body that it would normally show. This is very useful when working with RESTful APIs as there are a few scenarios (including this one where we create data) where you may not get body data returned but there is important information in the status code or headers.

The data should include a link field and a name field, so those are placed here in JSON. I constructed this simple data packet by hand, but you could easily have PHP do this for you. If you run into issues with generating valid JSON, try checking your data with the jsonlint.com website, which is a very handy tool.

The response comes back with a 201 “Created” status code so I know that my item has been successfully added to the collection. To fetch the item itself, I can follow the Location header in the response. Some APIs will also return the resource in the body of the response and that’s also valid. We refer to resources by their URIs; with that information, we can operate on this resource as we wish.

An alternative approach to using POST on a collection to create a new resource is appropriate in the situation when the consumer, rather than the server, sets the identifier of the new record. In this scenario, the representation of the new resource can instead be sent in a PUT request directly to the new URI. Care must be taken, when designing a system like this, to ensure that multiple consumers do not pick the same URIs, either causing conflicts or overwrites. At least make sure that these are dealt with in a sane way, perhaps using the 409 status code, which means “Conflict.”

Fetch a Resource or Collection with GET

To fetch representations of resources, use the GET verb applied to either a collection or an individual resource without sending any body content with the GET request. The resources will usually appear with exactly the same structure, regardless of whether they were requested within a collection or on their own. The status code will be 200 if the record(s) were successfully retrieved, although other “good” status codes may also be used here such as 302 “Found” or 304 “Not Modified” (more about caching in the next section when we discuss how to update records).

If, however, the record isn’t successfully found, a status code describing the problem will be returned. In a vast number of cases, this will be a 404 status code, to indicate that the record wasn’t found or doesn’t exist. If the user isn’t authenticated, a 401 “Not Authorized” status code may be returned; a user who has identified herself but doesn’t have permission to see this item may receive a 403 “Forbidden” instead. Any one of a number of other possible failure cases could also occur, and these should have the appropriate status codes associated with them.

The code for fetching either one or many records in our example wishlist service is quite simple, so we’ll view it all at once:

  case 'GET':
    if(isset($url_pieces[2])) {
      try {
        $data = $storage->getOne($url_pieces[2]);
      } catch (UnexpectedValueException $e) {
        throw new Exception("Resource does not exist", 404);
      }
    } else {
      $data = $storage->getAll();
    }
    break;

The check for the $url_pieces[2] variable is to allow us to distinguish between two types of URL:

If we only want a specific resource, then we ask the storage class for it, and if we can’t find it then we throw an exception so that the exception handler can return a sane message and importantly the correct status code to the user. If we want all the resources in a collection, then we just grab everything and this then goes out through the output handler. Similarly, a successfully found single resource is correctly formatted and returned by the output handler we already discussed.

Let’s look at examples of finding a record using cURL. These examples also use the Python JSON module for formatting, a technique which we’ll look at in more detail in Chapter 11.

First, the collection (so we can see which individual resources are available for us to pick):

$ curl -s http://localhost:8080/rest.php/items | python -mjson.tool
[
    {
        "link": "http://www.amazon.co.uk/My-First-Baby-Annabell-Doll/dp/B00FBWB9A2",
        "name": "doll",
        "url": "http://localhost:8080/rest.php/items/ed6f1"
    },
    {
        "link": "http://www.amazon.co.uk/dp/B00FWRIAUS/",
        "name": "notebook",
        "url": "http://localhost:8080/rest.php/items/b4fa2"
    },
    {
        "link": "http://www.amazon.co.uk/dp/B00MA3I0BG",
        "name": "travel organiser",
        "url": "http://localhost:8080/rest.php/items/7f868"
    },
    {
        "link": "http://www.amazon.co.uk/dp/B00FWRIAUS/",
        "name": "notebook",
        "url": "http://localhost:8080/rest.php/items/dd44d"
    }
]

And picking an individual record from the list (the notebook we added earlier):

$ curl -s http://localhost:8080/rest.php/items/dd44d | python -mjson.tool
{
    "link": "http://www.amazon.co.uk/dp/B00FWRIAUS/",
    "name": "notebook",
    "url": "http://localhost:8080/rest.php/items/dd44d"
}

The resources return the same representation whether they are fetched by themselves or in the collection, which is a key element of RESTful services. It’s also important to note that the resource includes information on how to represent itself, here using the url field. We’ll talk more about data formats in Chapter 12, but it’s important to note here that for service to be RESTful, information about how to reach a single resource should always be provided.

How about when a resource doesn’t exist? It’s important to consider failure cases as well as successful ones, and here’s how we handle that:

$ curl -v http://localhost:8080/rest.php/items/nonsense
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /rest.php/items/nonsense HTTP/1.1
> User-Agent: curl/7.38.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Host: localhost:8080
< Connection: close
< X-Powered-By: PHP/5.6.4-4ubuntu6
< Content-Type: application/json
<
* Closing connection 0
{"error":"Resource does not exist"}

I requested a resource that I knew didn’t exist and the API correctly returns a 404 status code. There is also an error message in the body of the response, which is a good opportunity to give more detail on an error (but don’t be tempted to put status information here!). There are some great standards for describing errors (including, for example, the SOAPFault responses), and similar standards are now being used with RESTful services; see Chapter 12 for examples of these standards and a discussion of how to implement them.

If your API implements rate limiting, then it might be that the resource exists and the user has permission to see it, but she has exceeded her allotted number of requests in a given time frame. In this situation, either a 420 “Enhance Your Calm” or 429 “Too Many Requests” would be a good status to return.

Some APIs (this includes GitHub) will return a 404 to indicate that the record exists but the requesting user does not have access to it. This makes it impossible to deduce the existence (or nonexistence) of a record without the rights to see it! Exposing such details is known as “leaking information” and in many settings it is something of which to be wary.

Update a Resource with PUT

To edit records RESTfully is a multistep process. First, the resource should be retrieved by GET. Then, the representation of the resource can be altered as needed, and that resource should be PUT back to its original URI. Even if only a small part of the record needs to be changed, REST deals with representations of resources, so the whole resource will be fetched and sent back for the update. Identical to when a resource was created using POST, the PUT request will include the resource representation in the body and the appropriate Content-Type in the header.

In the example application, the POST and GET examples have already been shown. Next, we can take that data and update it using a PUT request. In this trivially simple example, the code for PUT and POST is so similar that I placed them both in the same example, Example 8-2.

To see this code in action, here’s an example that updates the record we created earlier:

$ curl -v -X PUT -H "Content-Type: application/json" http://localhost:8080/rest.php/items/dd44d --data '{"link": "http://www.amazon.co.uk/dp/B00FWRIAUS/","name": "awesome notebook"}'
* Connected to localhost (127.0.0.1) port 8080 (#0)
> PUT /rest.php/items/dd44d HTTP/1.1
> User-Agent: curl/7.38.0
> Host: localhost:8080
> Accept: */*
> Content-Type: application/json
> Content-Length: 77
>
* upload completely sent off: 77 out of 77 bytes
< HTTP/1.1 204 No Content
< Host: localhost:8080
< Connection: close
< X-Powered-By: PHP/5.6.4-4ubuntu6
< Location: http://localhost:8080/rest.php/items/dd44d
< Content-type: text/html; charset=UTF-8
<
* Closing connection 0

The curl command shows my PUT request and includes the Content-Type header, the -v switch, and some JSON content, much like the POST example earlier. The main differences are the verb we use and also the fact that this time, the PUT request operates on a specific resource rather than requesting a new resource be created in a collection.

When updating a record, it’s quite common to include some identifying information for the contents of the resource, such as a Last-Modified header or an ETag, to allow us to check whether the resource changed as a result of something else between the GET and PUT, as this isn’t an atomic operation. This is closely linked to how cacheable different URIs are, which we covered in “Caching Headers”.

For a newcomer to REST, updating a representation of a whole resource can seem cumbersome when only a tiny part of it is actually changing, but don’t be tempted to diverge from this approach and break the RESTfulness of the design. If it really does seem like an alternative approach would be better, then you have two options: either create a subresource or use the PATCH verb.

Creating a subresource is simplest, if you want to change one field of a resource, and make that field available at its own URI. For example, if it seems like overkill to update a whole user record just to change an email address, then instead create a resource /user/42/email. This smaller resource can then be subject to GET, change, and PUT instead of fetching and then pushing back a whole user profile.

The alternative is to use PATCH to make a small change to an existing record. This is becoming more widely implemented and supported, and you’ll see examples in the GitHub API that has been used as an example elsewhere in this book. GitHub allows the user to make changes to individual fields in a record by supplying the data you want to change and making a PATCH request instead of a PUT request to the existing resource’s URI.

DELETE a Resource

This is the most damaging move, but it’s also the simplest. The DELETE verb is sent with a request to the URI of the item to be deleted, with no body content necessary. Many services will return 200 for “OK”—or simply a 204 for “No Content”—when an item was successfully deleted, and a 404 “Not Found” if the item didn’t exist. However, if the request was made to delete something, and the record doesn’t exist, many services see that as “success” and will return 200 or 204, regardless of what really happened (unless the record couldn’t be deleted for some reason, such as the user does not have the proper permission). This idea of always behaving in the same way each time the action is called is known as idempotency and is expected behavior for both GET and DELETE requests.

Our example wishlist service also offers DELETE (it’s up to you whether you actually delete a record or just set a deleted property and avoid returning it in future; this is just about the outward-facing implementation) and the PHP code is shown in Example 8-4.

Example 8-4. Example wishlist service handling a DELETE request in PHP
    case 'DELETE':
        $id = $url_pieces[2];
        $storage->remove($id);
        $storage->save();
        header("Location: http://localhost:8080/items", null, 204);
        exit;
        break;

All that happens here is that we work out which item we’re getting a DELETE request for, and then we ask the storage class to remove that and save itself. The 204 status code is just to let the client know that there is no content to return, and while there is also a Location header, this is entirely optional as the client will probably know where it wants to go next.

The DELETE method in action looks the same whether the resource existed or not:

$ curl -X DELETE -v http://localhost:8080/rest.php/items/958a9
* Connected to localhost (127.0.0.1) port 8080 (#0)
> DELETE /rest.php/items/958a9 HTTP/1.1
> User-Agent: curl/7.38.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 204 No Content
< Host: localhost:8080
< Connection: close
< X-Powered-By: PHP/5.6.4-4ubuntu6
< Location: http://localhost:8080/items
< Content-type: text/html; charset=UTF-8
<
* Closing connection 0

In many ways DELETE is much simpler than the PUT, PATCH, or DELETE methods since it does not include body data (and should not include body data). After deleting the resource, our service will return a 404 if it is requested again.

RESTful Versus Useful

REST is truly an elegant way to build services, and a nice way to work with data over HTTP. Not every application has requirements that are best met by a RESTful service, so don’t be tempted to make architectural decisions based on the current fashionable technologies. Standards are always an excellent thing to follow; they’ve been created by people who have implemented this several times and learned from their mistakes. That said, don’t be afraid to break the rules just as you would for any other architectural decision in software engineering. Many APIs are criticized because they are deemed “not RESTful.” While I recommend that you follow the strategies in this chapter, it’s acceptable for you to take inspiration from REST, rather than implementing it to the letter. Do make sure, though, that your API is still well documented, robust, and, most of all, useful.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset