Learning REST in pieces is one thing while applying all these concepts to real application development is completely another challenge.
This tutorial will teach us to design REST APIs for a network-based application. Please note that the takeaway from this whole exercise is learning how to apply REST principles in the application design process.
1. Identify the Resources – Object Modeling
The first step in designing a REST API-based application is identifying the objects that will be presented as resources.
For a network-based application, object modeling is pretty much more straightforward. There can be many things such as devices, managed entities, routers, modems, etc. For simplicity’s sake, we will consider only two resources i.e.
- Devices
- Configurations
Here configuration may be a sub-resource of a device. A device can have many configuration options.
Note that both objects/resources in our above model will have a unique identifier, which is the integer id
property.
2. Create Model URIs
Now when the object model is ready, it’s time to decide the resource URIs. At this step, while designing the resource URIs – focus on the relationship between resources and their sub-resources. These resource URIs are endpoints for APIs.
In our application, a device is a top-level resource. And configuration is a sub-resource under the device. Let’s write down the URIs.
/devices
/devices/{id}
/configurations
/configurations/{id}
/devices/{id}/configurations
/devices/{id}/configurations/{configId}
Notice that these URIs do not use any verb or operation. It’s crucial not to include any verb in URIs. URIs should all be nouns only.
3. Determine Resource Representations
Now that resource URIs have been decided, let’s work on their representations. Most representations are defined in either XML or JSON format. We will see XML examples as it is more expressive of how data is composed.
3.1. Collection Resource of Devices
When returning a collection resource, include only the most important information about that resource. This will keep the size of the response payload small, and so will improve the performance of the API.
<devices size="2">
<link rel="self" href="/devices"/>
<device id="12345">
<link rel="self" href="/devices/12345"/>
<deviceFamily>apple-es</deviceFamily>
<OSVersion>10.3R2.11</OSVersion>
<platform>SRX100B</platform>
<serialNumber>32423457</serialNumber>
<connectionStatus>up</connectionStatus>
<ipAddr>192.168.21.9</ipAddr>
<name>apple-srx_200</name>
<status>active</status>
</device>
<device id="556677">
<link rel="self" href="/devices/556677"/>
<deviceFamily>apple-es</deviceFamily>
<OSVersion>10.3R2.11</OSVersion>
<platform>SRX100B</platform>
<serialNumber>6453534</serialNumber>
<connectionStatus>up</connectionStatus>
<ipAddr>192.168.20.23</ipAddr>
<name>apple-srx_200</name>
<status>active</status>
</device>
</devices>
3.2. Single Device Resource
Opposite to collection URI, a single resource URI includes complete information about a particular device. It also includes a list of links to sub-resources and other supported operations. This will make your REST API HATEOAS driven.
<device id="12345">
<link rel="self" href="/devices/12345"/>
<id>12345</id>
<deviceFamily>apple-es</deviceFamily>
<OSVersion>10.0R2.10</OSVersion>
<platform>SRX100-LM</platform>
<serialNumber>32423457</serialNumber>
<name>apple-srx_100_lehar</name>
<hostName>apple-srx_100_lehar</hostName>
<ipAddr>192.168.21.9</ipAddr>
<status>active</status>
<configurations size="2">
<link rel="self" href="/configurations" />
<configuration id="42342">
<link rel="self" href="/configurations/42342" />
</configuration>
<configuration id="675675">
<link rel="self" href="/configurations/675675" />
</configuration>
</configurations>
<method href="/devices/12345/exec-rpc" rel="rpc"/>
<method href="/devices/12345/synch-config"rel="synch device configuration"/>
</device>
3.3. Collection Resource of Configurations
Similar to device collection representation, create configuration collection representation with only minimal information.
<configurations size="20">
<link rel="self" href="/configurations" />
<configuration id="42342">
<link rel="self" href="/configurations/42342" />
</configuration>
<configuration id="675675">
<link rel="self" href="/configurations/675675" />
</configuration>
…
…
</configurations>
Please note that configurations
collection representation inside device
is similar to top-level configurations
URI.
The only difference is that configurations for a device are only two, so only two configuration items are listed as subresources under the device.
3.4. Single Configuration Resource
Now, a single configuration resource representation must have all possible information about this resource – including relevant links.
<configuration id="42342">
<link rel="self" href="/configurations/42342" />
<content><![CDATA[…]]></content>
<status>active</status>
<link rel="very big raw configuration script" href="/configurations/42342/raw" />
</configuration>
3.5. Collection Resource of Configuration under a Single Device
This sub-collection of configurations will be a subset of the primary collection of configurations and will be specific to a device only.
As it is the subset of primary collection, DO NOT create a different representation data fields than primary collection. Use the same presentation fields as the primary collection.
<configurations size="2">
<link rel="self" href="/devices/12345/configurations" />
<configuration id="53324">
<link rel="self" href="/devices/12345/configurations/53324" />
<link rel="detail" href="/configurations/53324" />
</configuration>
<configuration id="333443">
<link rel="self" href="/devices/12345/configurations/333443" />
<link rel="detail" href="/configurations/333443" />
</configuration>
</configurations>
Notice that this sub-resource collection has two links. One for its direct representation inside sub-collection i.e. /devices/12345/configurations/333443
and other pointing to its location in primary collection i.e. /configurations/333443
.
Having two links is essential as you can provide access to a device-specific configuration in a more unique manner, and you will be able to mask some fields (if the design requires it), which shall not be visible in a secondary collection.
3.6. Single Configuration Resource under a Single Device
This representation should have either exactly a similar representation as of Configuration representation from the primary collection, OR you may mask a few fields.
This subresource representation will also have an additional link to its primary presentation.
<configuration id="11223344">
<link rel="self" href="/devices/12345/configurations/11223344" />
<link rel="detail" href="/configurations/11223344" />
<content><![CDATA[…]]></content>
<status>active</status>
<link rel="raw configuration content" href="/configurations/11223344/raw" />
</configuration>
Now, before moving forward to the next section, let’s note down a few observations, so you don’t miss them.
- Resource URIs are all nouns.
- URIs are usually in two forms – collection of resources and singular resource.
- Collection may be in two forms primary collection and secondary collection. A secondary collection is a sub-collection from a primary collection only.
- Each resource/collection contains at least one link i.e. to itself.
- Collections contain only the most important information about resources.
- To get complete information about a resource, you only need to access its specific resource URI.
- Representations can have extra links (i.e. methods in a single device). Here method represents a POST method. You can also have more attributes or form links in an altogether new way.
- We have not talked about operations on these resources yet.
4. Assigning HTTP Methods
So our resource URIs and their representation are fixed now. Let’s decide all the applications’ possible operations and map those operations to the resource URIs.
For example, a user of our network application can browse, create, update, or delete devices from the network and create/deploy/remove the device configurations. So let’s assign these operations to respective resources.
4.1. Browse all devices or configurations [Primary Collection]
HTTP GET /devices
HTTP GET /configurations
If the collection size is large, you can also apply paging and filtering. e.g., the below requests will fetch the first 20 records from the collection.
HTTP GET /devices?startIndex=0&size=20
HTTP GET /configurations?startIndex=0&size=20
4.2. Browse all configurations under a device [Secondary Collection]
HTTP GET /devices/{id}/configurations
It will be mostly a small-size collection, so there is no need to enable filtering or sorting here.
4.3. Browse a single device or configuration
To get the complete detail of a device or configuration, use GET
operation on singular resource URIs.
HTTP GET /devices/{id}
HTTP GET /configurations/{id}
4.4. Browse a single configuration under a device
HTTP GET /devices/{id}/configurations/{configId}
Subresource representation will be either same as or a subset of the primary presentation.
4.5. Create a device or configuration
CREATE is not an idempotent operation and in HTTP protocol – POST
is also not idempotent. So use POST.
HTTP POST /devices
HTTP POST /configurations
Please note that the request payload will not contain any id
attribute, as the server is responsible for deciding it. The response to CREATE request will look like this:
HTTP/1.1 201 Created
Content-Type: application/xml
Location: http://example.com/network-app/configurations/678678
<configuration id="678678">
<link rel="self" href="/configurations/678678" />
<content><![CDATA[…]]></content>
<status>active</status>
<link rel="raw configuration content" href="/configurations/678678/raw" />
</configuration>
4.6. Update a device or configuration
The update operation is an idempotent operation, and HTTP PUT is also an idempotent method. So we can use the PUT method for update operations.
HTTP PUT /devices/{id}
HTTP PUT /configurations/{id}
PUT response may look like this.
HTTP/1.1 200 OK
Content-Type: application/xml
<configuration id="678678">
<link rel="self" href="/configurations/678678" />
<content><![CDATA[. updated content here .]]></content>
<status>active</status>
<link rel="raw configuration content" href="/configurations/678678/raw" />
</configuration>
4.7. Remove a device or configuration
Removing is always a DELETE
operation.
HTTP DELETE /devices/{id}
HTTP DELETE /configurations/{id}
A successful response SHOULD be 202 (Accepted)
if the resource has been queued for deletion (async operation), or 200 (OK) / 204 (No Content) if the resource has been deleted permanently (sync operation).
In the case of async operation, the application shall return a task id that can be tracked for success/failure status.
Please note that you should put enough analysis in deciding the behavior when a subresource is deleted from the system. Usually, you may want to SOFT DELETE a resource in these requests – in other words, set their status INACTIVE.
By following this approach, you will not need to find and remove its references from other places as well.
4.8. Applying or Removing a configuration on/from a device
In a real application, you will need to apply the configuration on the device – OR you may want to remove the configuration from the device (not from the primary collection). You shall use PUT and DELETE methods in this case, because of their idempotent nature.
//Apply Configuration on a device
HTTP PUT /devices/{id}/configurations
//Remove Configuration on a device
HTTP DELETE /devices/{id}/configurations/{configId}
5. More Actions
So far, we have designed only object models, and URIs and then decided on HTTP methods or operations on them. You need to work on other aspects of the application as well:
1) Logging
2) Security
3) Discovery etc.
See the source code of this example application to understand it better.
Does a REST API require that a client can ask for a list of valid methods or valid resources from the server? How would the developer of a new client know what is allowed?
Use HATEOAS links and some API documentation. Due to the absence of strict guidelines, only HATEOAS may not be sufficient.
????????
@Steve Just send OPTIONS request to the server (check https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS)
Exemple: curl -X OPTIONS https://example.org -i
The response then contains an Allow header that holds the allowed methods:
HTTP/1.1 204 No Content
Allow: OPTIONS, GET, HEAD, POST
Cache-Control: max-age=604800
Date: Thu, 13 Oct 2016 11:45:00 GMT
Server: EOS (lax004/2813)
Yes, that’s one good way. Unfortunately, there is no such mechanism for communicating the availability of resources.
While the example of “/devices/{id}/configurations/{id}” used in this article looks nice but it’s not valid. This is because duplicate parameter names are not allowed. So it should be something like:
/devices/{id}/configurations/{configurationId}
Thanks for the feedback. Yes, during implementation this will be an issue. Fixing it here.
Use PATCH if you need to partially update the existing element. SO Thread
Hello there,
The POST HTTP method has been indicated as a preferred to perform creation of objects on a RESTFULL interfce. However, on https://restfulapi.net/idempotent-rest-apis/, it is indicated that PUT should be preferred in detriment to the POST method due to the idempotent aspect and the fact that there would occur repeated calls (intentional or not) and the interface would then render prone to errors.
So, for the sake of consistency, shouldn’t this aspect be noted on this page as well? I’d go for PUT rather than POST, anyway.
What if I have setup API for all dropdown options when I about to edit device, do I need to do like this
HTTP GET /devices/info —— return all available status and all other options
above URI will be crashing directly with HTTP GET /devices/{id} which is quite ambiguous.
or
HTTP GET /device_infos —— return all available status and all other options
which is the right design to follow?
If you apply right level of caching at server side, no GET URI will be crashing. I will say the first design is more suitable.
How about restricting “id” a bit?
instead of {id:.*}, use {id:\d+} or {id:!info} or anything that will exclude “info” as a valid ID and fallback to a different controller.
As far as design goes, /devices/info should return a list of “info” resources on all devices.
GET /devices HTTP/1.1 – return all devices
GET /devices/123 HTTP/1.1 – return device with ID 123
GET /devices/123/info HTTP/1.1 – return all info on device with id 123
GET /info HTTP/1.1 – return all available info
GET /devices/info HTTP/1.1 – this returns… all available info (as above)?
This all depends on how you data is structured as well
Great article. It’s only a pity it addresses only a many to many relationship between devices and configurations. I am also looking for something with a one to one and a one to many relationship. Anyone have any ideas for these? I am also a little disappointed in the response “Not really. Until I apply configuration to device, how configuration id will be created?”
It sounds like configuration has 2 ids. one for it’s primary existence and one for it’s many to many relationship with device.
Exactly. Configuration has 2 ids in that sense. One primary id and another id which helps in finding the device and configuration mapping.
I see where you’re coming from, Vernon. I was confused when I saw PUT used to create a configuration resource. Earlier, we read that POST is proper for a create operation, and PUT is proper for an update operation. And then, right at the end of the article, we see PUT used to create a resource. I wondered if that might be because it’s an update, but that would mean we have an id.
Clearly, we don’t have an id. This leads me to believe that the PUT is going to create a resource if no configuration exists or update if a configuration does exist. Allowing PUT to create a resource as a side effect is problematic because it leaves the API user no mechanism to explicitly update a resource without possibly creating a resource. I’ve used APIs that allow PUT to create when no resource is found, and that forced me to test the result of each PUT and DELETE if a resource was created. Ugh.
I’m very clear in my API designs. POST creates. PUT updates. If PUT finds no resource, it fails. No side effects and no surprises.
When it comes to 1-1 or many-many, I’ve done both. For many-many, the device has an id and the resource has an id. There’s no reason for the relationship to be exclusive. In fact, the relationship between the two is usually captured in an associative table. By convention, the associative table is named using the two participating resources, so that would be DEVICE_CONFIGURATION. In the associative table, we would keep the id for the device, the id for the configuration, and the id for the entity containing both. One row from that table looks like this:
Holding true to form with POST and PUT, a POST on a configuration would create both a configuration and an
deviceConfiguration
. A PUT would fail if there is nodeviceConfiguration
containing the device id and the configuration id.For 1-1, I represent that as a contained resource. It looks like:
Feedback is welcome. Does anyone have a different approach that they prefer?
POST creates. PUT updates. If PUT finds no resource, it fails. No side effects and no surprises.
Practically, I have also followed same approach in all my designs.
Looking at the example code above :
//Apply Configuration on a device
HTTP PUT /devices/{id}/configurations
Here, PUT is used instead of POST, because the operation is done ON A device which clearly has an id, where idempotent characteristic can occur.
This is different from common create operations on collection resource which use POST like so :
HTTP POST /configurations
Should
HTTP PUT /devices/{id}/configurations
be
HTTP PUT /devices/{id}/configurations/{id}
?Not really. Until I apply configuration to device, how configuration id will be created?
It’s a PUT – you already know the id. You want update a specific configuration…
PUT request, is only used if we try to update something. Here you said yourself that you want to add/apply a configuration to a specific device. So shouldn’t the URI be like this: PUT /devices/{id}
and we should provide that Configuration id/information(already existed in the primary collection of configurations) in the request body.
OR
If the configuration does not already exist in the primary collection of configurations, the configuration should be created first and then added/applied to the device like this:
POST /configurations——Response body contains configuration id
PUT /devices/{id}———-Request body contains configuration id
I think this is the better way for designing these type of REST resources.
You should do the following:
HTTP POST
/devices/{id}/configurations
To create a configuration resource for the specified device resource.
HTTP PUT
/devices/{id}/configurations/{id}
For adding an existing configuration to an existing device
HTTP POST
/configurations
To create a configuration without a specified device.
And so on. That would be the RESTful way to go.
PUT to create a resource is a proper deviation to the whole standardization principle behind REST; if you don’t follow the protocol to a fault, how will you expect your consumers to understand your service? If you start to make deviations like this, there really isn’t any point in making REST APIs.
| Should
|
| HTTP PUT /devices/{id}/configurations
| be
|
| HTTP PUT /devices/{id}/configurations/{id}
| ?
Yes, it absolutely should. PUT method is used to create or overwrite a resource *at a particular URI*, and crucially that means *that it is already known by the client*. HTTP POST /devices/{id}/configurations would be perfectly valid. However a PUT to that URI implies you are overwriting the resource *at that specific URI*, and not a child resource. Not sure why `Admin` is purporting to be an expert on this subject and authoring a website about REST when they clearly aren’t familiar with even the most rudimentary characteristics of REST.
No. Your syntax clearly suggests that there is already a specific id for configuration resource on which you want to update. So it is different from what the author meant with his example.
The other possible syntax option for create operation is using POST :
HTTP POST /devices/{id}/configurations
However, here the author mentioned that the operation was done on a specific device, where idempotent nature could happen. So the author suggested the recommended syntax which I agree.
Thank you, It’s Very useful