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.
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.
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 },
...
]
}
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
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
{}
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" }
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
}
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 }
]
}
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
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:
https://api.github.com/user/repos?page=2&per_page=100
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:
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.
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)POST statuses/update
(online ref)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.
POST /2/users/:id/likes
DELETE /2/users/:id/likes/:tweet_id
GET /2/users/:id/liked_tweets
GET /2/tweets/:id/liking_users
POST /2/users/:id/retweets
DELETE /2/users/:id/retweets/:source_tweet_id
GET /2/tweets/:id/retweeted_by
Another well defined example of REST API is (GitHub REST API).