REST APIs demystified
Learn about the well-known though often misunderstood concepts behind REST principles and RESTful APIs.
Working as a web developer, the day necessarily came when you faced the need to exchange data between browser and server, and heard about REST(ful) APIs.
REST stands for Representational state transfer and is a data-exchange architectural style strongly based on HTTP protocol.
It was originated in 2000 by Roy Fielding (co-author of HTTP specification and co-founder of Apache HTTP server) in a dissertation, which is still considered as of today like a major contribution to Web standards.
It's worth mentioning that REST is a collection of principles not published by any means as a standard or specification. This fact is probably the top reason why junior developers often struggle to efficiently grasps its concepts.
A RESTful API — or more commonly a REST API — is an API trying to follow REST principles as much as possible.
Philosophy / Concepts
REST defines data exchanges between 2 entities as an "interop". An interface allowing the parties to follow an established contract in a conventional thus predictable way.
The terminology used here involves :
- Resources : similar to objects in OOP or tables in relational database : a group of data properties related to each other. Example : an
Animal
- Operations : similar to functions or methods in coding. Example :
Creating an Animal
- Ids : resource identifiers, can be of any type but has to be literal, and unique.
Key principles
Often when reading about REST, one land on [the well-known contraints](https://restfulapi.net/rest-architectural-constraints/) of REST architecture.
But I find those concepts hard to grasp, and way broader than API concepts. To keep things focused on our topic, I'll try here another approach, more focused on the contract between client and server.
Resource-identifiers URIs
In REST, the URI has to be meaningful, structured, and representing the resource we want to manipulate. In other words, the rule is to put the resource name in the URI.
Best conventions suggest resource names to be in kebab-case (hyphen separated lowercase), in their plural form.
One level down
The two basic shapes are single resources (identified by their Id) and resources lists :
Route | Resource |
---|---|
/animals |
the list of Animals |
/animals/23 |
the Animal with id 23 |
Two levels down
An extended shape can express nested resources. Similarly to database foreign keys :
Route | Resource |
---|---|
/animals/23/toys |
the Toys of Animal 23 |
/animals/23/friends |
friends list (Animals) of Animal 23 [1] |
/animals/23/owner |
the owner (Human) of Animal 23 [2] |
For this special case, the Animal resource is referencing itself. (just like self-referencing tables in database). A common pattern is to derogate from "express resources by names" and rather express linked ones by link name.
(here,friends
instead ofanimals
) ↩︎For cases like this where the nested ressource is a single one (just like
1:1
relationship in database), we also derogate from the "plurals" rule.
(here,owner
instead ofowners
) ↩︎
More levels down ?
I personally like to limit "nested-patterns" to 2 levels down; because further digging is a nonsense to me.
Indeed, imagine such a resource identifier :
Route | Resource |
---|---|
/animals/23/friends/21/toys |
The toys of Animal{23} friend Animal{21} |
Coding such a call requires you to know the end Animal id
(21
)...
so why not simply requesting it's toys ?
Route | Resource |
---|---|
/animals/21/toys |
The toys of Animal{21} |
You can safely extrapolate that for any use-case : this kinda "resets" at any level. I personally never encountered a blocking use-case.
— But I'd love hearing in comments if you already did !
An exception ?
A decent exception use-case from what I just said are rare cases when you want to manipulate the association itself between 2 entities. In that case you need the 2 id
s written down.
Route | Resource |
---|---|
/animal/23/owner/1 |
the association (ownership relation) between Animal{23} and Human{1} |
/animal/23/friends/25 |
the association (friendship relation) between Animal{23} and Animal{25} |
(We'll cover this case in a second with examples)
HTTP methods
Meaningfulness doesn't stops at resources, but also expands to operations. As such, in REST they are expressed using HTTP method verbs.
GET
is used to find or listPOST
is used to create (or sometimes "associate", see below)PATCH
is used to update (partially)PUT
is used to update (totally replace)DELETE
is used to delete (or sometimes "dissociate", see below)
But with years, a common derogation arised
PATCH
is nowadays rarely usedPUT
is commonly used for partially updating instead
Let's review common use-cases by operation
GET
use-cases :
Route | Effect |
---|---|
GET /animals |
List Animals |
GET /animals/23 |
Find Animal{23} |
GET /animals/23/toys |
List Animal{23} Toys |
GET /animals/23/owner |
Find Animal{23} owner (Human) |
POST
use-cases
Route | Effect |
---|---|
POST /animals |
Create an Animal |
POST /animals/23/toys |
Create an Toy and add it to Animal{23} Toys list |
POST /animals/23/toys/10 |
Add Toy{10} to Animal{23} Toys list |
POST /animals/23/owner |
Create a Human and set it as Animal[23] owner |
POST /animals/23/owner/69 |
Set Human{69} as Animal{23} owner |
PUT
use-cases
Route | Effect |
---|---|
PUT /animals/23 |
Modify Animal{23} |
PUT /animals/23/owner |
Modify Animal{23} owner (Human) |
PUT /animals/23/owner/70 |
Replace Animal{23} owner with Human{70} |
DELETE
use-cases
Route | Effect |
---|---|
DELETE /animals/23 |
Delete Animal{23} |
DELETE /animals/23/toys/10 |
Dissociate Toy{10} from Animal{23} |
DELETE /animals/23/owner |
Dissociate owner Human from Animal{23} |
Single-resource endpoints
As you already guessed, an important root principle of REST is that endpoints should manipulate a single given resource type. It means either a list of records of this resource type, or a single one record.
This applies to the body (both Response and Request) of operations :
GET /animals/23
This returns the requested Animal object.
GET /animals
This returns the Animal list.
POST /animals
This takes an Animal — often without id
if auto-generated
and returns the created Animal object.
PUT /animals/24
This takes an partial Animal — only properties to update
and returns the modified Animal object.
DELETE /animals/23
Returns the deleted Animal object.
(Same logic goes for linked objects — that I exclude here for simplicity.)
HTTP Status codes
Again with meaningfulness in mind, responses should include standard and semantic HTTP status codes. This applies to both success and errors.
Success
201
- created : for every creation success (POST
)200
- ok : for every other success (GET
,PUT
,DELETE
)
Errors
404
- not found : for inexisting objects (mainly targeted byresource/{id}
routes, but also single linked objectsresource/{id}/linked-object
)400
- bad request : for invalid bodies in creations or updates (POST
,PUT
)409
- conflict- for already existing objects in rare cases when creating a resource directly with a forced id in body or URL (
POST /resource/:id
) - for already occupied association 1:1 when trying to created one (
POST /resource/:id/linked-resource/:id
)
- for already existing objects in rare cases when creating a resource directly with a forced id in body or URL (
410
- gone : means "deleted", for rare cases of requesting deleted object on servers tracking deleted objects (soft delete mecanism)401
- unauthorized : for reserved resource when trying to access them without authentication403
- forbidden : for reserved resource when trying to access them with unsufficient permission500
- internal server error : for internal errors not to be shown to a user, or unknown errors503
- service unavailable : for cases when the route is temporarily unavailable (external resource down, overload, etc.)
Wrapping up
As a sumup, applying the "Don't Reinvent the Wheel" principle, I propose you further readings that inspired me white writing this post.