5-3 REST Web API

REST is the most popular way to design Web APIs. In a REST Web API, actions are designed around resources. Each resource is identify by a URL. A client applies the standard HTTP verbs (including GET, PUT, POST, DELETE, POST) on the resources to perform CRUD operations (Create, Retrieve, Update, Delete).

For example, in an online shopping site, there may be resources about products, customers and their orders. These are represented by the following URLs.

resource type collection individual item
product /products /products/id
customer /customers /customers/id
order /orders /orders/id

In this lab, we’ll demonstrate how to do the following using a hypothetical REST API.

  • retrieve a single product
  • list all products
  • update an existing product
  • delete a product
  • create a new product

Retrieve a single item

To retrieve information about the product with a given ID, a client uses a request similar to this.

GET /products/5 HTTP/1.1
Host: example.com

The Web API returns the product data in JSON format as follows.

HTTP/1.1 200 Ok
Content-type: application/json
Content-length: 116

{
  "id": 5,
  "productName": "Coke candy",
  "category": "Confections",
  "unitPrice": 10.5,
  "unitsInStock": 20
}

In principle, REST APIs should reuse HTTP status code as much as possible to report errors. For example, if the server cannot find a product of the given ID, it may return 404 error as follows.

HTTP/1.1 404 Not found

However, the server often needs to report more detail about the errors, or there may not be suitable HTTP status code to report some errors. We’ll discuss how to handle this later.

Retrieve all items in a collection

To list all products, use the GET verb on the product collection resource.

GET /products HTTP/1.1
Host: example.com

The Web API returns the product data as follows.

HTTP/1.1 200 Ok
Content-type: application/json
Content-length: 6060

{
  "count": 98,
  "items": [
    { "id": 5, "productName": "Coke candy", "unitPrice": 10.5 },
    { "id": 6, "productName": "Chocolate bar", "unitPrice": 25.0 },
    ...
  ]
}

Update an item

HTTP provides a verb PUT to replace / update an existing resource in the server. The request body must contain a representation of the whole resource.

PUT /products/5 HTTP/1.1
Host: example.com
Content-type: application/json
Content-length: 142

{
  "id": 5,
  "productName": "Coke candy (Update price and stock)",
  "category": "Confections",
  "unitPrice": 12.5,
  "unitsInStock": 32
}

Sometimes, we may only need to update a few properties of a resource with many properties. HTTP provides another verb PATCH to list only the properties to be updated in the request payload. For example, the following request updates the unit price only.

PATCH /products/5 HTTP/1.1
Host: example.com
Content-type: application/json
Content-length: 21

{ "unitPrice": 13.8 }

After processing the PUT or PATCH request, the server has to report success / failure. If the update is successful, the server often returns the most up-to-date version of the resource.

HTTP/1.1 200 Ok
Content-type: application/json
Content-length: 142

{
  "id": 5,
  "productName": "Coke candy (Update price and stock)",
  "category": "Confections",
  "unitPrice": 13.8,
  "unitsInStock": 32
}

When you design the Web API, you may also choose to just return a code for successful processing, and don’t include an updated representation, as follows.

HTTP/1.1 204 No content

Delete an item

HTTP supports a verb DELETE to delete a resource.

DELETE /products/5 HTTP/1.1
Host: example.com

If successful, the server returns 204, or 200 with an empty object.

HTTP/1.1 204 No content

or

HTTP/1.1 200 Ok
Content-type: application/json
Content-length: 2

{}

Error reporting

In principle, REST APIs should reuse HTTP status code as much as possible to report errors. For example, if the server cannot find a product of the given ID when the client sends a GET, PATCH or DELETE request, it may return 404 error.

HTTP/1.1 404 Not found

Some designers use HTTP status codes like 400 and 500 to report runtime error. An example is GitHub API client error. Web APIs often describe the errors in the JSON payload using some specified fields, e.g. errorCode. Therefore, a client can always check the existence of any error, and the detail of the error, from the specified fields in the payload.

HTTP/1.1 400 Bad Request
Content-type: application/json
Content-length: 62

{ "errorCode": 1001, "message": "Date format error in input" }
HTTP/1.1 500 Internal Server Error
Content-type: application/json
Content-length: 63

{ "errorCode": 1002, "message": "Database connection problem" }

Since the error detail is already available in the JSON payload, some Web APIs always return the responses using 200 status code. This is convenient for client programming because all information about the error can be found in the JSON payload, and programmers do not need to check the HTTP status code.

HTTP/1.1 200 Ok
Content-type: application/json
Content-length: 63

{ "errorCode": 1002, "message": "Database connection problem" }

Create an item

In theory, one can create an item by sending a PUT request. However, you need to know the ID of the new item in order to build the resource URL. This is not always possible because the ID is usually assigned by the server.

A common solution in REST Web APIs is to POST to the collection resource for the new item. (Notice the ID is not given.)

POST /products HTTP/1.1
Host: example.com
Content-type: application/json
Content-length: 113

{
  "productName": "Great new chocolate",
  "category": "Confections",
  "unitPrice": 20,
  "unitsInStock": 100
}

After creating the new item, the server returns a successful response with the location of the newly created resource in location header. The status code 201 means that a new resource has been created.

HTTP/1.1 201 Created
Location: http://example.com/products/98

Web APIs are often designed to return the representation of the newly created resource in the response body.

HTTP/1.1 201 Created
Location: http://example.com/products/98
Content-type: application/json
Content-length: 126

{
  "id": 98,
  "productName": "Great new chocolate",
  "category": "Confections",
  "unitPrice": 20,
  "unitsInStock": 100
}

Resources with more complicated structure

The JSON representation of a resource may be more complicated than a list of properties of simple values. For example, an order contains several lines, each line specifies a product and its quantity.

GET /orders/10248 HTTP/1.1
Host: example.com

HTTP/1.1 200 Ok
Content-type: application/json
Content-length: 302

{
  "id": 10248,
  "customerId": 85,
  "orderDate": "2012-07-04",
  "freight": 32.38,
  "shipCity": "Reims",
  "lines": [
    { "productId": 11, "unitPrice": 14,  "quantity": 12 },
    { "productId": 42, "unitPrice": 9.8, "quantity": 10 },
    { "productId": 72, "unitPrice": 34.8,"quantity": 5 }
  ]
}

Searching items

If the searching is limited a single kind of resources (e.g. products), we can extend the list item endpoint (e.g. GET /products) with search criteria formatted as a query string.

For example, you can introduce two parameters category and maxUnitPrice in your API to support product search for a certain category and a unit price less than a given price.

GET /products?category=Confections&maxUnitPrice=10 HTTP/1.1
Host: example.com

Pagination

The result of searching or listing items often consists of a large number of items, which may overwhelm a client in computing resource consumption. Web APIs often support pagination, i.e. breaking the search result into several batches (or pages).

A familiar example of pagination is web search. For example, here are the URLs of the first three pages when search ‘Web API design’ in bing.com.

The above technique is called offset-based pagination. Typically, there are two parameters, the first to specify the offset (index of the first item to include in this page), and the second to specify the number of items in this page.

Two examples of offset-based pagination in Web APIs:

When the items include a temporal property (e.g. post time of tweets) or other properties that are ever increasing (e.g. tweet id in Twitter), you can also paginate using these properties as the offset. Example: GET https://api.twitter.com/2/tweets/search/recent?start_time=2021-10-07T00:39:00Z&max_results=20


Another technique is called cursor-based pagination. Number of items to return in a page is specified in a query parameter (e.g. max_results). When the API server returns a response, it includes a token next_token, which the client can include in the next request to indicate the start position of the next page. (Similarly, the API also provides previous_token) for navigation to the previous page.

The following example is from the Twitter API endpoint to retrieve followers of a certain user. (online ref).

To get the first 3 followers of the user with id 12345678:

GET /2/users/12345678/followers?max_results=3 HTTP/1.1
Host: api.twitter.com

The Web API returns a response with a JSON payload similar to the following.

{
  "data": [
    { "id": "7000001", "name": "..", "username": ".." },
    { "id": "8003004", "name": "..", "username": ".." },
    { "id": "3030003", "name": "..", "username": ".." }
  ],
  "meta": {
    "result_count": 3,
    "next_token": "DEFGABCDJKKLL"
  }
}

To fetch the next page of the result, you can then use the following request.

GET /2/users/12345678/followers?max_results=3&pagination_token=DEFGABCDJKKLL HTTP/1.1
Host: api.twitter.com

Further information:

Sub-resources / nested route

Consider the Web API at https://jsonplaceholder.typicode.com/ which exposes the resource users and their posts and albums. Sending a GET request to /users and /posts will retrieve a list of users and posts respectively. Very often, a client needs to find the posts written by a certain user. Some API designer model such 1 to many relationship with sub-resource (also known as nested routes). A GET request to get the posts by user 1 would be https://jsonplaceholder.typicode.com/users/1/posts .

GET /users/1/posts HTTP/1.1
Host: jsonplaceholder.typicode.com

This sample Web API also model the relationship that a post may have many comments, a user may have many albums, and an album may include many photos.

More examples of REST Web APIs

REST style is used in many Web APIs. A popular API is the Twitter API V2. Postman collection

  • GET /2/users/:id
  • GET /2/users/:id/tweets (online ref)
  • GET /2/tweets/:id (online ref)
  • GET /2/tweets/search/recent (online ref)
  • The Twitter API v2 currently (Oct 2021) does not provide an endpoint for posting tweets.
    • In the API v1.1, it is POST statuses/update (online ref)
    • But in API v2, it would be more natural to use POST /2/users/:id/tweets

A limitation of REST APIs is that only a limited set of standard HTTP methods (e.g. GET, PUT) can be used on resources. To model other actions, the Twitter API uses sub-resources to represent actions.

  • likes (online ref)
    • POST /2/users/:id/likes
    • DELETE /2/users/:id/likes/:tweet_id
    • GET /2/users/:id/liked_tweets
    • GET /2/tweets/:id/liking_users
  • retweets (online ref)
    • POST /2/users/:id/retweets
    • DELETE /2/users/:id/retweets/:source_tweet_id
    • GET /2/tweets/:id/retweeted_by
  • Also check the endpoints for ‘follows’

Another well defined example of REST API is (GitHub REST API).