To understand RESTful web services, let's consider the case study of a blog where we will discuss resources/entities in a blog. We will start to define the requirements and URLs for the blog's resources and then define responses that we should have against those requests. So these endpoints and response definitions will help us understand how RESTful web services endpoint should look like and what the response should be. In the later chapters, we will talk more about the implementation of these endpoints, so these definitions will act as an API document for the next chapters. However, for simplicity, we will keep it minimal for now and later add more attributes to it.
Note
Although based on HATEOAS, a RESTful web service should return links to the next endpoints and there are conventions that tell us about other endpoints but the API document is still important. API consumers (client-side developers) and API providers (server-side developers) should agree on it so that both can work in parallel without waiting for the other. However, in the real world, we don't have to write API document for basic CRUD operations.
In a typical blog, the most common resources are posts and comments. There are others as well but for now, we will discuss these two resources for the sake of understanding RESTful web services. Note that we are not considering authentication related stuff but will look into that in the later chapters.
Note
If client-side and server-side teams are of the same organization, working on a single app, then it is a good idea to get such document created by the client-side team as the server-side team is just the serving client side.
Here, we are listing the requirement a blog post its endpoints. For those endpoints, we will write a request and a response.
A blog post can be created, modified, visited, and deleted. There also be a way to list all the blog posts. So we are going to list the blog post's endpoints.
Here are the endpoints blog post:
- Request:
POST /posts HTTP/1.1
- Body parameters:
Content: This is an awesome post Title: Awesome Post
- Response:
{id:1, title:"Awesome Post", content:"This is an awesome post", link: "/posts/1" }
- Response code: 201 Created
Here POST
is the method, /posts
is the URL (path after the server name) and HTTP 1.1 is the protocol. We will keep using the same way of mentioning requests in examples as well. So, the first part of the request is the HTTP method, the second one is the URL and the third part is the protocol.
The response code tells the client that the resource has been created successfully. If a request parameter is missed by mistake, the response code should be 400, which represents a bad request.
- Request:
GET /posts/1 HTTP/1.1
- Response:
{id:1, title:"Awesome Post", content:"This is an awesome post", link: "/posts/1" }
- Response code: 200 OK
Note, if a blog post an ID provided (in the current case, 1) does not exist, it should return 404, which means resource Not Found.
- Request:
PATCH /posts/1?title=Modified%20Post HTTP/1.1
- Response:
{id:1, title:"Modified Post", content:"This is an awesome post", link:"posts/1" }
- Response code: 200 OK
Note, if a blog post with the ID provided (that is 1 in this case) does not exist, it should return the response code 404 that means resource not found.
Also, we have used PATCH instead of PUT for the since PATCH is used to modify all or some attributes of a record while PUT is used for modifying a whole record just like replacing an old record with a new one. So, if we use PUT and pass only one attribute, the other attributes will become empty. In the case of PATCH, it will only update the attribute that is passed and other attributes untouched.
- Request:
DELETE /posts/1 HTTP/1.1
- Response:
{success:"True", deleted_id:1 }
- Response code:
200 OK
- Request:
GET /posts HTTP/1.1
- Response:
{ data:[ { id:1, title:"Awesome Post", content:"This is an awesome post", link: "/posts/1" }, { id:2, title:"Amazing one", content:"This is an amazing post", link: "/posts/2" } ], total_count: 2, limit:10, pagination: { first_page: "/posts?page=1", last_page: "/posts?page=1", page=1 } }
- Response code:
200 OK
Here, data is an array of objects as there are multiple records returning. Other than total_count
, there is a pagination object as well, and right now it shows the first last pages because total_count
for records is only 2. So, there is no next or previous page. Otherwise, we should also have to show the next and previous in pagination.
As you can see, there are links in the pagination as well as the post's links in post objects. We have included these links in response to being compliant with the HATEOAS constraint, which stated that if the client knows about an entry point, it should be enough to discover relevant endpoints.
Here, we explored the requirements of blog posts and defined the request and response of their endpoints. In the next entity/resource, we are going to define endpoints and responses in comments.
Here, we are listing the requirements of a post comment and its endpoints. For those endpoints, we will write Request
and Response
.
There will be one, more than one, or comments on posts. So, a comment can be created on a blog post. A blog post's comments can be listed. A comment can be read, modified, or deleted.
Let's define endpoints for these requirements.
Here are the endpoints the post's comments:
- Request:
POST /posts/1/comments HTTP/1.1
- Body parameters:
comment: An Awesome Post
- Response:
{id:1, post_id:1, comment:"An Awesome Post", link: "/comments/1"}
- Response code:
201 Created
Here in the case of a comment, a comment is against a certain blog post. So, the request URL includes post_id
as well.
- Request:
GET /posts/1/comment/1 HTTP/1.1
orGET /comment/1 HTTP/1.1
The second one seems more reasonable as in that one only to have a comment's ID without worrying about the post ID of that comment. And since a comment's ID is unique, we don't need to have the post's ID to get the comment. So, we will proceed with the second URL that is GET /comment/1 HTTP/1.1
.
- Response:
{id:1, post_id:1, comment:"An Awesome Post", link: "/comments/1"}
- Response code:
200 OK
Since any comment can only exist against some post, the response includes post_id
as well.
- Request:
PATCH /comment/1?comment="Modified%20Awesome%20Comment' HTTP/1.1
- Response:
{id:1, post_id:1, comment:"Modified Awesome Comment", link: "/comments/1"}
- Response code:
200 OK
Here, we used PATCH as we want to update a single attribute of a that is a comment. Also, you can see %20
passed in a new comment. So, %20
is just a replacement for a space as a URL cannot contain spaces. So with URL encoding, spaces should always be replaced by %20
.
- Request:
DELETE /comments/1 HTTP/1.1
- Response:
{success:"True", deleted_id:1 }
- Response code:
200 OK
Note, if a post comment with an ID provided (in the current case, 1) does exist, it should return 404Not Found.
- Request:
GET /posts/1/comments HTTP/1.1
- Response:
{ data:[ { id:1, comment:"Awesome Post", post_id:1, link: "/comments/1" }, { id:2, comment:"Another post comment", post_id:1, link: "/comments/2" } ], total_count: 2, limit: 10, pagination: { first_page: "/posts/1/comments?page=1", last_page: "/posts/1/comments?page=1", page=1 } }
- Response Code:
200 OK
As you can see, a post's comments listing is very to a blog post's listing. And it has total_count
and pagination in the same way. It shows the first and last page right now because total_count
for the records is only 2. So there is no next or previous page. Otherwise, we should also have to show the next and previous links in pagination.
Normally, you don't see pagination with comments on blogs, but it is better to keep it consistent to have pagination in the listing. Because there can be lot of comments for a post, we should apply some limit to it, so we will need pagination.