Let’s start with the following: this is a post about a part of REST called HATEOAS, and why your API probably doesn’t need it. If you don’t know what that is — especially if you also feel that you do know what REST is — or, you think your API should implement HATEOAS, this post lies in a subset of {informative, angering, boring} for you.
I ran across Zapier’s When REST Gets Messy post, and among other good advice, it had a buried gem regarding HATEOAS:
I’m also not convinced that it makes sense for an internally consumed API.
…why is this a gem? And what on earth is this HATEOAS acronym we’ve seen 3 times in two paragraphs? Read on to find out.
HATEOAS (pronounce it however you like — I use both hate-ee-ohs and hate-yo-ass) is one requirement of a RESTful API. The requirement is that a RESTful API should use Hypertext As The Engine Of Application State.
Acronym city. So what on earth does that mean?
Roughly this: each response from your API should include a representation of all validly reachable states. Probably as a set of URLs describing what can be done next — kinda like a dynamic site map, but in an API.
HATEOAS is kind of obscure
Most people don’t know what HATEOAS is. Many have never even heard of it — including a surprising number of people who claim to have written RESTful APIs. This is because it’s a bit of a complex topic, and can only be addressed after getting people to grok some more basic requirements of REST. Also… there’s no standard way to implement it — but I’ll cover that nit in a bit.
Most people learn about REST by reading a tutorial or article, and unfortunately end up with the following understanding of it:
- Use HTTP verbs — at least GET, POST, PUT, and DELETE (poor PATCH and HEAD get no love)
- Your database tables (e.g. “posts”, “comments”) are “Resources”, and you should expose an endpoint for each one. The HTTP verbs map well to CRUD (Create is POST, Read is GET, Update is PUT, Delete .. DELETE) actions on them.
Those are steps on the way to a RESTful web API, but it also needs some HATEOAS sauce. Whether HATEOAS is left out of articles or people just stop reading before getting to it is unknown to me, but they frequently seem to miss it.
The two points above roughly define a resource oriented API. And you know what? That is good enough for most applications.
Resource oriented is good enough
Once again, it isn’t REST, but it’s good enough for most APIs. Writers of APIs often just desire a way to organize their data and their interaction with it. A resource-oriented API provides exactly that. They’re also using HTTP verbs, so web crawlers can’t mess up their data with errant GET requests. I think this is a big win.
But… shouldn’t they do all of REST? What exactly would HATEOAS gain them? (let alone that there are other components to REST) Here is my thesis: HATEOAS is useful for solving a problem that is more complex than what most API implementers have to deal with.
As I said before, HATEOAS basically gives a list of all the links available to you. If you’re writing both an API and its client or client library, you really don’t need that. You already know what links are available. If they change, it’s you who changed them, and you who will make corresponding changes in your API-consuming clients. Most of us are not writing APIs for public consumption, and if we are, we’re writing fairly stable, versioned APIs that are typically accessed via libraries, by purpose-written integrations or apps.
Now we see why that sentence from When REST Gets Messy is a gem. An internally consumed API is a simpler problem than the one HATEOAS solves.
What problem does HATEOAS solve?
HATEOAS lets you do something truly cool: with it, you could write a client that has no knowledge of an API aside from how to read its site-map. A business with several teams that all make APIs (internal or external) might need this, because any client might have to speak to many different APIs. The business has a choice:
- The client can know every detail of every API,
- Every API can work the same way, or
- Every API can be described the same way.
The last choice is obviously a very good idea; it’s like a meta-API. Everyone agrees on one way for the APIs to describe themselves, and then one client library can know how to parse that description format.
It’s now okay for the APIs to be totally unstable. Clients are not constantly needing updates, because they aren’t tied to a particular data representation or quirk. The HATEOAS representation gives enough information for the client to figure out what it needs. Google or Yahoo or Amazon are the type of place for whom this is a real problem.
It’s sort of the magical dream that one day, all APIs everywhere will be self-describing, and all clients will know how to parse the descriptions and figure everything out for themselves. Then we can stop being as concerned about API stability and versions, and writing clients or fixing API bugs would conceivably be way easier.
Sounds great. Why not just implement it and go full REST?
Unfortunately, HATEOAS has some problems. The worst of which is that you have to come up with your own way to describe your API, because REST just tells you to do it, not how to do it. To my knowledge, there’s no known good way to describe all possible APIs yet — so you’re on the hook for that.
Then the smaller problem: adding in self-descriptions takes extra work. Just writing a client that parses them can be a lot of work.
There are some attempts at making a standard, but we’ve all seen that xkcd. If one takes off, people could be writing general purpose API consumers, and that would be cool. For now though, there are a million ways to describe your API, so put yourself in the shoes of someone writing a client. You have the following choice:
- Pay close attention to the HATEOAS implementation of the API and write a consumer to parse it and figure out what to do dynamically.
- Pay close attention to the Resource implementation of the API and write a consumer to interact with it, and update it when needed.
The latter option is much easier up-front. So consumers rarely use HATEOAS, even if it’s present. Now, put yourself back in the shoes of an API writer: is it worth spending the time to a) come up with a description format and then b) implement it? In practice, most API writers end up in one of the following situations:
- Spend extra time to write a beautiful, fully RESTful API and have no one really use the HATEOAS components.
- Bolt a crappy implementation of HATEOAS on to your resource-oriented API and have no one really use it.
- Actively choose not to provide a HATEOAS representation because you know no one would use it anyway.
- Blissfully remain ignorant of the fact that your non-HATEOAS API is not a RESTful API.
And some rare organizations fall into this category:
5. Spend extra time to write a beautiful, fully RESTful API that you and your
customers use, because your constellation of APIs are constantly in flux.
You probably don’t need REST.
It’s a fun mental game to play, and it can be smart to include links in your API responses to ease a client’s work. Fully representing your application’s state in every response is almost always overkill — so until we have solved how to do HATEOAS everywhere, a resource-oriented API is good enough.