Introduction
When we first created a REST API in our Tungsten Clustering suite, most of the Continuent Team members had no experience in writing such an interface.
As most newcomers to this technology, we faced the question: how to write a great REST API?
Why Is It So Hard?
With a little more experience now, my personal analysis of the problem is that the pain comes from three facts:
- REST APIs are primarily meant for CRUD applications, where you find a direct mapping Create/POST, Read/GET, Update/Put and Delete/DELETE
- Beyond this usage, for more complex applications, you have to do everything with an incredibly limited syntax: GET, PUT, POST, DELETE
- There are no rules, specification or standards for it
All you can find are a couple of webpages where "experienced users" have put down hints or best practices. Don't get me wrong, these were of great help, and I am very grateful to the authors of these pages for their enlightenment! Still, there are no laws you can stick to and be sure to do it right if you blindly obey. Moreover, looking at forums and conversations, some questions don't really get an answer with a consensus.
One of the first questions most people will come up against when writing their API is: should I use PUT or POST?
While reading data or state is pretty obviously made through a GET call, there were so many debates on when to use PUT or POST that I suggested to the team something really simple: use POST everywhere!
It was a deliberate choice, we knew it would probably not be to the taste of everyone, but it had the huge benefit of making things really simple! Even now, given that we were in a hurry and that the API was mostly for internal use, I think this was the right decision.
It allowed us to put our energy on a much more important topic:
Security
The very first question you should answer when setting up a REST API is: "will this create a security hole?"
The answer is short and simple: yes! - yet, there are a few things you can do to minimize the risks:
-
Listen to
localhost
by default
By not exposing your rest API to the world, you will limit DoS attacks and lower the risk of intrusion -
Enable SSL by default
No need to explain why you do not want clear-text data on the wire, especially with JSON-based, ASCII characters going through! -
Enable authentication by default
Even if that may add complexity to client applications, they should at least provide a username and password (security tokens being much better) -
If possible, fully disable your API by default
Customers who don't need it will not have to disable it… -
If possible, don't allow risky operations
It's obvious once said: if your API doesn't allow users to do any harm, it's harder for hackers to cause any
Once security is taken care of, we could put our focus on the individual calls, which we did in a...
New Release!
A few months back, with our upcoming new release version 8, and its new User Interface, we decided to rework our REST API entirely.
With a bit more time in front of us and the experience of the v2 API, we had a great opportunity to clear the frustration we had in not doing things perfectly.
So we went back to our studies, read blogs, StackOverflow conversations, W3C specs... and finally came up with what we consider is a great REST API.
Of course, we kept the security foundation intact, even if we had to enable the API v8 by default and add a couple of sensitive calls, the rest of the recommendations above still apply.
On top of that, we have fully reworked the PUT and POST logic. Again, it hasn't been easy, and we made a few compromises, but we're really happy with what we came up with, so here is another set of hints compiled for convenience:
Avoid Using Verbs
That's a rule of thumb you can find on most articles: use nouns, not verbs.
PUT /api/v8/manager/services/{service}/datasources/{datasource}/state { "value": "online" }
Rather than creating one call per new state ("/online", "/offline"), use a more generic entry point "state" with its new value as payload
Sometimes, It's Not Possible. Undertake It, Make a Compromise
Some "actions" in our management system made it difficult to find a good generic noun: "backup", "restore"... We even started to implement a generic "action" entrypoint where everything was given through payload ({"actionType": "backup", "actionParameter": "mysqldump"}
).
That quickly became a payload nightmare since our client applications were hard to rewrite… that's where we realized we were going too far...
So we ended up accepting a few verbs in the endpoints:
PUT /api/v8/manager/services/{service}/recover
POST /api/v8/manager/services/{service}/switch
Use POST to Create Resources, PUT to Update Them
I haven't found a use for it in our interface, but you can also think about PATCH
if you're just slightly modifying a resource.
Use PUT for Idempotent Actions
When doing an action on a resource, even with a nice noun and input payload, you WILL balance between PUT
and POST
. One rule that helped me choose: if two consecutive calls trigger the same final state, use PUT
. If not, use POST
. That's how the "recover" operation above became a PUT
(recovering the same service a second time will leave the service in the same state), and "switch" operation a POST
(another switch in the same data service might pick a different node to become the new primary)
Use Plurals
Any resource accessed via an ID or name should be via a preceding plural, whether it's a read or write operation:
GET /api/v8/connector/services/{service-name}
PUT /api/v8/connector/listeners/{listener-id}
Use DELETE Wherever You Can
Removing a user, a resource, an entry in a table, that's all DELETE. But limit them if they can harm your system
Document Your API
Whatever decision or compromise you make, the most important thing to do is to document your choices. At Continuent, we have both a great Overall API documentation and use the Enunciate tool to generate Technical docs from our Java code.
Wrap up
When writing a REST API, while individual calls matter and are the real interface to your customers applications, the most important, yet first step to be taken is to make sure the security aspects are covered.
During the development of our most recent software release (V8), we had a chance to rework our previous API and we are thrilled with what we came up with. Don't hesitate to request a demo so you can try out the full product!
Smooth Sailing!
Comments
Add new comment