arrow-rightgithublinkedinscroll-to-topzig-zag

Making API calls that don't involve resources in REST

Last Updated On

It's no news that REST, in essence, is focused around the concept of resources. In some cases, it's more intuitive to create a mental mapping between a business item and a "RESTful" resource which makes API development a breathe. For example, a photo can be viewed as a resource on Instagram, a document in Google Docs, a user in a social network, a tweet on Twitter, and so on. However, there are other situations where the parallel might not be so straightforward. That type of scenarios usually fall under "processing" categories or "actions": "pay", "translate", "trade", "encrypt/decrypt", "encode/decode", etc.. When we operate in these terms we might be tempted to lean towards a non-RESTful style where an endpoint starts to represent a "method or function" instead of a resource (e.g. /pay, /encrypt). This is not a wrong solution and simply called RPC (Remote Procedure Call) and is a great alternative to REST (more on that later). Since it's best to avoid a mixture of multiple concepts such as "resources" and "actions" within one API, let's take a look at how we can "re-think" or "fit" that type of functionality into existing REST API.

Solutions

I would like to start with /pay example:

  1. Convert verbs into nouns. That is probably one of the most natural strategies, therefore, is used more widely:

    POST /payments
    Host: payments-api.com
    {
      "amount": 100.00
      "currency": "USD"
      ...
    }

    Here /pay endpoint is substituted with /payments. The operation adds a "payment" to "payments" collection. The real-world illustration of the applied method can be found in Stripe api where instead of a word "payments" they use synonym "charges".

    However, there are other circumstances where the current approach would be less transparent. Imagine a "hypothetical encode API". How would you represent an encoding action with a resource-based collection model? /encoding, /encoders ? That is where another technique comes into play.

  2. Determine the response of an action. One of the good names for the resource returned as a result of encoding operation can be cipher:

    POST /cipher
    Host: encoding-api.com
    {
      "data": "HelloWorld"
      "schema": "base64"
    }

    The solution is aligned with REST principles around resources, but not as much with "Cacheability" and "Uniform Interface" as a result of which may not be considered truly RESTful:

    • Since each request containing the same combination of "data" and "schema" returns the same response it should be cachable. Even though according to latest HTTP standard POST requests can be cached (caches also can be different - "private" cache such as browser or "shared" cache such as nginx), it goes against REST principles of "idempotency": POST operations should always reach a server to create a new resource, always return a different result (with id) therefore they are not cachable from the REST point of view.

      On the technical side of things cache configuration for the "body" and "URI" combination requires additional setup compare to default and a more simplistic variant with "just URI". This functionality may be not even supported in some load-balancers/proxies.

    • Goes against REST Uniform Model. When we POST resources in REST they are not always, but typically hold unique content (going back to our example with users in Facebook, tweet in Twitter or photo on Instagram) with an intent that a new resource will be created. Looking at the encoding example from the business of view is nothing more than retrieving a cipher, rather than creating it.

  3. Refactor to utilize GET and Content Negotiation with a custom Accept-Schema header:

    GET /cipher/HelloWorld
    Host: encoding-api.com
    Accept-Schema: base64
  4. Refactor to utilize GET:

    GET /cipher/base64/HelloWorld
    Host: encoding-api.com

While examples 3 and 4 can probably satisfy most demanding REST advocates they often will go against business requirements. Take a look at our "encoding API": how should we handle string with special characters/whitespaces (e.g. "Hello World $&")? Does the conversion into Hello%20World%20%24%26 on the client and then back to Hello World $& justifies REST "purity"? More than that, if we replace our "hypothetical encode API" with "hypothetical encryption API" it becomes clear that solutions 3 and 4 will not be even an option for security reasons (can't have sensitive data in the URI).

To complete the list of all available options, I would like to mention one more, lesser-known approach:

  1. Use GET request with body:

    GET /cipher
    Host: encoding-api.com
    {
      "data": "HelloWorld"
      "schema": "base64"
    }

    Yes, message body is allowed in HTTP GET request. It also seems to be solving "conceptual idempotency" by applying more appropriate semantical operation, but this solution should be used with caution as it has following downsides:

    • From the technical side, cacheability setup is still not ideal as it needs to be implemented using "URI" and "body" compare to "just URI" as in example 4
    • Not completely aligned with Uniform Model. A resource should be identified by its URI only (HTTP Spec) and the body should not have semantic meaning to the request (Roy Fieldings) source
    • GET with the body is not widely spread combination and may not be supported by the technology of API consumer

    Nonetheless, the aforesaid approach is used by a very popular search engine Elasticsearch. They do, however, provide a fallback mechanism in a form of POST requests which becomes identical to example 3.

Recap

We took a look at a couple of approaches that can help align action-based API's or parts of the API's to be more geared towards REST architecture. There is one very important part here that I can't stress enough: even though some of the proposed solutions have their downsides, all of them would solve a business problem in one way or another. When thinking in terms of a business domain it's important to weigh the pros and cons and take the solution that has more advantages. It depends on what kind of problem we're trying to solve - is the goal to deliver certain functionality to API consumers? Maybe it makes sense to sacrifice REST compliance and go with solution 3 or 5? On that note, I would like to point out Stripe example to show that in some cases it's possible to deviate from core REST principles by using verbs in the URL:

I also like the fact that they don't consider them fully restful, but rather "organized around REST":

That's a great wording that not only highlights the core principles but also notes that it has the freedom to go with a different solution when there is a need. On the other hand, the target business goal might be formulated as "to be fully RESTful" therefore proper choices should be made.

Finally, I would bring one more consideration to the table. At the very beginning of this post, I used the term "fit" not by accident. If the target API is action-based maybe we're trying to adopt a REST in a place where it is not a good fit? Maybe it's worth skipping over REST and consider, an alternative, HTTP-JSON-RPC style? That's the model of a Slack's API where it utilizes HTTP as a transport, JSON as a message format, and METHOD_FAMILY.method endpoint convention. Since covering nuances and details of RPC-style API is a different topic, I want to share this smashingmagazing article that has a great overview of this problem.

Resources