Concept
Base things in Rest Api
Rest Api is stands for Representational State Transfer.
Basic things in Rest Api design
- Resource - an object which has identifier
- Method - an operation which indicates that what kind of state transfer will happen
- State - state is current representation of resource, it means that our resource can have different data in different time, and it is state of resource
In Rest Apis resources are identified by their urls, so it means that 1 unique url = 1 unique resource
We have Server and Clients, we can transfer state of resource from server to client or vice versa.
What does state transfer mean?
Let’s say we want to create new user. How we can understand this basic operation from state transfer perspective?
Creating new user means that, our client has new state (new user), and our client needs to transfer its new state about user resource to server.
So, we can say that creating new user is transfer new user state to server
After we transfer state of resource to server or vice versa, both party should have same state if an external factor does not cause any change
It means that if we updated a resource, and we called GET method to get same resource, we should see same data as we sent request to update it
Don’t write as Restful, Think as Restful
Restful service should be abstract, not representation of backend service layer as is.
You need to think on restful service independently, don’t try to implement Rest Api’s just like converting service layer codes to APIs, instead think from API consumer perspective
Rest APIs are consist of resources and CRUD operations on this resources
Try to be proactive, if there are some struggling points how you can design Rest Api for specific case, just think how it can be translated to crud operation, and how consumer can easily understand the design
Use nouns instead of verbs in endpoint paths
Incorrect variant:
GET /getUserById/15
This endpoint is wrong if we take into consideration that, in Rest APIs endpoints(paths) are resource identifiers, so /getUserById/15 as identifier does not make any sense
Correct variant:
GET /users/15
If now make sense, GET the resource which identifier is /users/15
Name collections with plural nouns
We should name our collections as plural
GET /users
<- we can think this as we are getting list of users
GET /users/1
<- we can think this as we are getting user number one from list of users(previous collection resource)
If we use singular names, it will be confusing
GET /user
<- it may be misunderstood as we are getting single user
GET /user/1
<- it may be misunderstood as we are getting user’s sub resource named 1
Examples:
GET /users
GET /users/1
GET /users/1/orders
Map Correct HTTP Methods to REST Operations
Method | Usage | Example |
---|---|---|
GET | Get Resource | GET /users GET /users/1 GET /users/1/star |
POST | Create Resource | POST /users POST /users/1/star |
PUT | REPLACE/Full Update | PUT /users/15 {full body} |
PATCH | Partial Update | PATCH /users/15 {partial body} |
DELETE | Delete Resource | PATCH /users/15 |
Nesting resources for hierarchical objects
Nested/Sub resources are good choice to handle relations
GET /users/1/orders <- nested resource
GET /orders?userId=1 <- not nested resource, filtered by userId
When to use nested resource?
- If nested resource can be created/updated/queried only if you have information about its parent
- Sub resource is logically related to its parent
Convert Actions to Resources or Fields
Let’s say we have user resource, and we want to enable/disable, we want to give star to this user
Simple but not desired:
POST /users/15/add-star
POST /users/15/remove-star
POST /users/15/activate
POST /users/15/deactivate
What instead we can do?
Solutions 1: Converting actions to sub-resources
We can think as our user resource has a sub resource named activated (adverb form of active) and if we create this sub resource we say that we activate user, if we remove this sub resource we say that we deactivate user
GET /users/15/activated <- 200 -> activated, 404 -> dectivated
POST /users/15/activated <- this will activate user
DELETE /users/15/activated <- this will deactivate user
Say idea for starring
POST /users/15/star <- this will star the user
DELETE /users/15/star <- this will unstar the user
Basically what we’re trying to do is to make our actions look like Rest API, so translate actions to resources
Solution 2: Converting actions to fields
In this solution we think that activated and star are not sub resource, they are properties of our user
GET /users/15 RESPONSE: {...activated: true}
PATCH /users/15 REQUEST BODY: {activated: true}
Handle errors gracefully and return standard error codes
Http status as Error code
*In rest API we should use HTTP status codes to describe what the problem is about* Main Idea for status codes:
- 2** (mainly 200 is used) status codes are for describing that operation is succeeded
- 3** redirection happened, the resource which you are trying to operate is moved to another resource identifier or moved to another server
- 4** client data are incorrect, client sent something wrong, need to fix it
- 5** server cannot process the problem, 5xx errors indicate that the problem cannot be fixed from client perspective and something corrupted server
In ideal world 5** error should not be happened, if we are returning 5** status codes, it means that we have some fault in application code or in our infrastructure
Let’s go to detailed status codes (most used error codes by REST APIs)
Status Code | Message | Rest Api Usage |
---|---|---|
2** | ||
200 | OK | Updating Resource with PUT OR PATCH method |
201 | CREATED | Creating Resource with POST Method |
204 | NO_CONTENT | Resource deleted, as we not returning any data in DELETE, it is better to indicate that there are no content |
4** | ||
400 | BAD_REQUEST | POST/PUT/PATCH, why creating/updating request, request input validation failed, client sent data which is wrong from servers perspective |
401 | UNAUTHENTICATED | User is not authenticated and this resource needs user identification (you need to login or send access token, etc.) |
403 | FORBIDDEN | User is identified but don’t have access to this resource or cannot call this method in this access because of insufficient permission |
404 | NOT_FOUND | Resource not found, we should return 404 if the resource we are operating on not found |
405 | METHOD_NOT_ALLOWED | Resource not found, we should return 404 if the resource we are operating on not found |
5** | ||
500 | INTERNAL_ERROR | We should use that if we have some found on application codes |
Notes:
you should not use 404 if the resource which is you are accessing in backend logic not found, 404 is for when consumer try to do some operation on resource and that resource not found.
Example:\
POST /restaurants\
{
"user": {
*"id": 13 <- we don't have a user with id=13*
}
}
In this case we should not return 404 as the resource we are operating on is restaurants, what we could not find is different resource
Instead we may use 400 Bad request with message user not found with id=13
Error message
Besides, status codes whe should generalize error response, we should return structured data for describing what went wrong
for 400 errors it is better to show field errors alongside with field names like:
{
"message" : "name, age fields rejected",
"rejectedFields": [
{
"field": "name",
"message": "must not be blank"
},
{
"field": "age",
"message": "must be between 0 and 150"
}
]
}
Allow filtering, sorting, and pagination
It is not important to handle filtering, sorting, pagination for all resources, this requirements should met consumer needs.
But it is better to handle filtering, sorting and pagination for resources, and it is required to do it consistently
Filtering:
Filtering can be handled by two approach:
- Filter by query parameters:
/users?age=13
Using filtering by query parameters is the best way, you may use filtering by query parameters if you can support multipple and multichoice filtering options like users can be searched by age, name, or both - Filter by path parameters:
/users/by-age/13
This approach is good to choice if you are few and dedicated filtering options, like you can only filter by age or by name
Sorting:
/users?sort=+age,-name
Pagination:
/users?page=1&pageSize=10
Aggregation in Rest Api
It is a little hard to create better API for resource aggregation, but we have some solutions for that:
Let’s say we have users resource collection (GET /api/1.0/users
).
How we can get resource count and average age by country?
Solution 1: create specialized sub resource
In this solution we just create new sub resource directly from collection resource without item resources, and separate aggregate sub resource from resource itself by aggregate keyword Formula:
/api/1.0/{resource-name}/aggregated/{aggregate-name}
Example:
GET /api/1.0/users/aggregated/by-country
Response:
[
{
"country": "Azerbaijan",
"averageAge": 24
"count": 563
},
{
"country": "Italy",
"averageAge": 26
"count": 2638
}
]
Solution 2: create new resource
In this solution instead of creating new resource we will create a separate resource:
Formula:
GET /api/1.0/{aggregated-resource}
Example:
GET /api/1.0/users-by-country
Response:
[
{
"country": "Azerbaijan",
"averageAge": 24
"count": 563
},
{
"country": "Italy",
"averageAge": 26
"count": 2638
}
]
Second solution is simpler from first one and more precise to Rest Api Architecture, but the single problem here is that, in this approach you lose relation between original resource and aggregated resource
Filtering Before and After Aggregation
Get aggregated list of user countries for Africa continental and show results which has average age 24
GET /api/1.0/users/by-continental/{id:id_for_africa_continental}/aggregated/by-country/by-average-age/24
How we can create Rest Api for aggregation of some resources
Bulk Operations in Rest API
Sometimes it is required to do bulk operation do increase performance
Let’s check some bulk operations in Rest API world:
Remove sub-resources of resource OR Remove everything in collection
DELETE /users
<- this will cause to delete entire collection, to delete all its items
Bulk create resources and/or update
PATCH /users
<- this will check all sent items one by one, and if the resource is exists it will update it, if not create it
Bulk replace resources
PUT /users
<- this will update all users to given state (given in request body) and remove everything else like replacing resource with given
Api versioning
Api versioning and handling versions with caution is a must in Rest API world.
Example for a version:
/api/1.0/users
if we go production with version 1, and we need to do some breaking changed to version 1 (which is affecting rest api design) we need to introduce new version
/api/1.1/users
Use Hateoas
What is hateoas?
Hateoas stands for Hypermedia as the Engine of Application State
Example:
GET /api/1.0/users/1
{
"username": "taleh123",
"firstName": "Taleh",
"lastName": "Ibrahimli",
"_links":{ <- hateoas links
"self":{
"href":"http://localhost:8080/api/1.0/users/1" <- resource self link
},
"orders":{
"href":"hhttp://localhost:8080/api/1.0/users/1/orders" <- link to orders
}
}
}
Hateoas is very useful to build self descriptive REST APIs.