Appearance
Using REST
Since Dr. Roy Fielding introduced the REST (Representational State Transfer) architectural style in his doctoral dissertation in 2000, REST has quickly replaced the complex and cumbersome SOAP, becoming the standard for Web APIs.
What is a Web API?
If we want to retrieve a product from an e-commerce website, entering http://localhost:3000/products/123
will display the product page for the item with ID 123. However, this result is an HTML page that mixes Product data with its presentation. While this is fine for users, it becomes challenging for machines to parse the Product data from HTML.
If a URL returns data that is easily parsable by machines instead of HTML, that URL can be considered a Web API. For example, accessing http://localhost:3000/api/products/123
and directly returning the Product data allows machines to read it easily.
REST is a design pattern for APIs. The most commonly used data format is JSON. Since JSON can be read directly by JavaScript, RESTful APIs written in JSON format are simple, readable, and easy to use.
Benefits of Writing APIs
APIs encapsulate the functionality of a web application, allowing for significant separation between frontend and backend code. This makes backend code easier to test and frontend code simpler to write.
Additionally, if we consider the frontend page as a client for presentation, the API serves as the interface for providing and manipulating data for the client. This design achieves high scalability. For instance, when users need to purchase products on mobile devices, developing clients for iOS and Android that access the API can replicate the functionality offered by the browser without requiring changes to the backend code.
When a web application offers functionality via an API, its structure expands to:
Viewing web pages as clients is a key aspect of the scalability of REST architecture.
REST API Specification
Writing a REST API essentially involves creating async functions that handle HTTP requests. However, REST requests have a few unique characteristics compared to standard HTTP requests:
- REST requests are standard HTTP requests, but for requests like POST and PUT (aside from GET), the body must be in JSON format and the Content-Type should be
application/json
. - REST responses return results in JSON format, thus the response Content-Type is also
application/json
.
The REST specification defines a common access format for resources. Although not mandatory, adhering to this specification enhances understanding.
For example, a product is a type of resource. The URL to retrieve all Products is:
GET /api/products
To retrieve a specific Product, for instance, the Product with ID 123, the URL is:
GET /api/products/123
To create a new Product using a POST request, the JSON data is included in the body:
POST /api/products
To update a Product, for example, updating the Product with ID 123, the URL is:
PUT /api/products/123
To delete a Product, for instance, deleting the Product with ID 123, the URL is:
DELETE /api/products/123
Resources can also be organized hierarchically. For example, to get all reviews for a specific Product, use:
GET /api/products/123/reviews
When we need to limit the result set to a subset of data, we can use parameters. For example, to return the second page of reviews with ten items per page, sorted by time:
GET /api/products/123/reviews?page=2&size=10&sort=time
Handling REST with Koa
Since we are using Koa as a web framework to handle HTTP requests, we can also respond to and process REST requests in Koa.
We copy the previous project and rename it to koa-rest
, then prepare to add REST APIs.
For the controller, we can return the following content to be considered a REST API:
javascript
ctx.body = {
id: 12345,
name: 'Bob',
description: 'A rest api'
};
When Koa detects that ctx.body
is assigned a JavaScript object, it automatically converts this object into a JSON string for output without any additional configuration or code.
Tip
ctx.body
is a reference to ctx.response.body
, and both are equivalent.
We will add two REST APIs to signin.mjs
:
GET /api/users/:id
: Get user information by ID.POST /api/signin
: Send a POST request and return the login result.
The async function to get user information is as follows:
javascript
// /api/users/:id
async function user_info(ctx, next) {
let id = ctx.params.id;
if (id === '12345') {
ctx.body = {
id: 12345,
email: 'admin@example.com',
name: 'Bob'
};
} else {
ctx.body = {
error: 'USER_NOT_FOUND'
};
}
}
The async function to handle login requests is as follows:
javascript
async function signin(ctx, next) {
let email = ctx.request.body.email || '';
let password = ctx.request.body.password || '';
if (email === 'admin@example.com' && password === '123456') {
console.log('signin ok!');
ctx.body = {
id: 12345,
email: email,
name: 'Bob'
};
} else {
console.log('signin failed!');
ctx.body = {
error: 'SIGNIN_FAILED'
};
}
}
When an error occurs, the returned information includes the error
field, which the client relies on to determine if an error has occurred.
Finally, export the URL handling functions:
javascript
export default {
'POST /api/signin': signin,
'GET /api/users/:id': user_info
};
Now, we can directly test the GET request in the browser:
Inputting an invalid ID returns an error:
For POST requests, we cannot directly test them in the browser, but we can use the curl command as follows:
bash
$ curl -H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"123456"}' \
http://localhost:3000/api/signin
This will return:
json
{
"id": 12345,
"email": "admin@example.com",
"name": "Bob"
}
Inputting an incorrect password returns an error message:
bash
$ curl -H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"invalid"}' \
http://localhost:3000/api/signin
This will return:
json
{
"error": "SIGNIN_FAILED"
}
Since we have switched from traditional POST forms to REST, the frontend page needs to write JavaScript code to send REST requests. The modified HTML is as follows:
html
<!-- Add onsubmit callback to the form -->
<form id="signin-form" onsubmit="return signin()">
...
</form>
The JavaScript to send the REST request is as follows:
javascript
function signin() {
// Get form input:
let form = document.querySelector('#signin-form');
let email = form.querySelector('input[name=email]').value;
let password = form.querySelector('input[name=password]').value;
// Data for the REST request:
let data = {
email: email,
password: password
};
// Send the request:
fetch('/api/signin', {
// Send as POST:
method: 'POST',
headers: {
// Set Content-Type to JSON:
'Content-Type': 'application/json'
},
// Serialize the data to JSON:
body: JSON.stringify(data)
}).then(resp => {
// Parse the JSON data upon receiving a response:
resp.json().then(result => {
// Parsed data:
console.log(result);
// Check if there is an error field:
if (result.error) {
alert(`Sign in failed: ${result.error}`);
} else {
// Successful login, retrieve the name field:
alert(`Welcome, ${result.name}!`);
}
});
});
// Must return false to cancel the browser's automatic form submission:
return false;
}
As we can see, handling REST requests in Koa is very straightforward. The bodyParser()
middleware can parse the JSON data from the request and bind it to ctx.request.body
. By assigning a JavaScript object to ctx.response.body
, we complete the handling of REST requests.