Recently, while building the forest’s API and finding myself running into problems I’d never imagined would need to be solved, I realized that I know very little API design.
This implies I’ve learned some, and simply have a better view of the field than I did. This article (which concerns both design and implementation) discusses the things I have learned and contended with.
I’ve been reading https://geemus.gitbooks.io/http-api-design/content/en/, and studying a couple different APIs (e.g. GitHub, which is my favorite) to see how it’s done.
APIs need to be versioned so that the developer can easily release new work. The two major schemes to expose different API versions: through request headers, and through the URI.
The header solution is the “most correct”, partially because it keeps URIs as an explicit and clean reference to a particular resource. Properly, the API version doesn’t have any bearing on the resource the client is trying to access, and thus doesn’t belong in the uniform resource identifier. That’s a sound argument.
However, several major players do use “api/v1/resource”, the improper and less technically RESTful method. And I’m doing it, too.
It’s easier for development. I don’t have to use curl or Postman to see what my responses look like. I can just visit localhost in browser. I also believe it’s a less opaque technique, and the only argument against it is an appeal to authority.
Here’s a good StackOverflow answer explaining why I’m wrong.
I agree that URIs should be permalinks, but a major API version change is likely going to introduce breaking changes. API consumers are going to have to change how they request the resource in any case. I’m much happier keeping the version totally in sight.
URIs dependent on params, not IDs
I want clear, beautiful URIs. Locations are best known by their Cartesian coordinates, not their IDs. Any Rails developer already knows that this is somewhat of a pain. Although custom routes are fairly trivial in Rails, these schemes can very quickly and easily get out of hand if not managed properly; such is Rails’ dependence on naming conventions.
I went through a couple thoughts on this, like for example /locations/x/1/y/2, but I eventually settled on a query string: locations?x=1&y=2
I found a couple of people discussing whether query strings are RESTful on StackOverflow. It would seem that there’s no reason for them not to be; REST merely asks that resources have unique endpoints.
Of course, dictating to Rails that I want every location’s URI to use the query string rather than an ID meant clobbering DMM’s opinions.
Here’s some of the weirder stuff I had to do to get this scheme to work:
That bullshit concerning rails-api and JBuilder that I already wrote about.
It makes no sense that this isn’t built into rails-api. The main draw of Rails is that it’s opinionated, and thus does a bunch of heavy lifting for the developer at the cost of flexibility.
There’s a bunch of stupid rails-api stuff I’ve complained about on Twitter.
This is a large topic that dictates much of the forest’s design. It’s taken a lot of work, and I barely have it in place. HATEOAS is an aspect of REST that most APIs do not bother with; developers (not unreasonably) expect consumers to rely on separate documentation to get around. GitHub is my model API because they actually do implement it, and they implement it well.
I would love some back-end framework that makes this easier, like Rails makes every other common task easier. That’s a niche I would have expected rails-api to fill. Yes I’m bitter.
HATEOAS breaks down into a few sub-issues.
- Return relative or absolute URIs?
- Where should actions be in the response? All at the top level in one list or object, or some actions per object?
- What do action routes look like?
So, 1), I’m going with relative, rather than absolute, URIs. A couple of the reasons for this are, unfortunately, because Rails makes it easier to use relative URIs. URL helpers like
[resource]_url return relative URIs by default. Additionally, RSpec makes it hard to
get(absolute_url), so I’d have to add a bunch more boilerplate to the tests.
Here is a good discussion on paths on StackOverflow.
Github uses absolute URIs for action routes. They say it’s so the client doesn’t have to construct the URI themselves. Fair enough; it does take a little work for the client to construct URIs with the scheme I’m using. However, they would probably only need to implement that solution once in that project. I don’t expect it to be too onerous.
2) Right now, actions in the API response are presented like this:
Actions are stored in
location.actions, and in
location.objects[i].actions. They are organized according to what the object the action is on, but as a result are scattered throughout the JSON.
I could also have a big ol’ hash or list at the top level simply labelled
actions which would contain every action possible at that moment. It would be very easy to find, and namespacing actions within could help organize them.
My main concern here isn’t whether one thing is more correct that the other. I’m struggling with what solution would be easiest for the API consumer.
In terms of development from the API maintainer’s perspective, the current setup is easiest. So that’s that. If it turns out to be awful from the consumer perspective, I’ll look into changing it.
3) And what should actions look like? Specifically, let’s say hello to a wolf. Is that:
I’m pretty sure I’m gonna go with the second option, although if the forest ever has nested resources, it could get to be a problem. The solution is to say “no nested resources!”, but that seems pretty limiting. We’ll see!
Designing and building this API has taught me so much that I didn’t know I didn’t know. It’s been so much fun! Because my collaborators (looks like Ryan‘s on board!) and I are basically the only people consuming the API, I have a pretty good idea of what the client’s needs are. Having to deeply consider both sides has given the process a lot of helpful direction.