Thoughts on API design

I recently had the opportunity to have a discussion about API design and
RESTful services with Vasusen Patil. I’ve also been having quite a
few conversations of similar nature with Justin Davis over the last
few months. These types of conversations are some of my favorite because, for
the most part, there are no right answers and many many interpretations.

I’ve been on both sides of these conversations as of late. At work, I am in the
trenches with API consumption. With my PHP framework, Pickles, I
have been in the middle of a rework that would move away from being a system
for building websites and moving towards a system for building services.

At work I have been given the opporunity to see how upwards of 30+ different
companies with similar products would implement the same API system. Even with
OpenTravel standards, there is still quite a divide on how to do things.
At this point, I’ve got my own thoughts and ideas on how to do things. I wanted
to get them out of my head and into my blog for posterity’s sake. Let’s be
honest, as engineers, our ideals tend to change over time and it’s always good
for a laugh to look back at things 😉

Endpoints

I’ve been guilty of it in the past, but these days I will do whatever I can to
keep any verbs out of the endpoint names. Somewhat trivial, but I prefer my
endpoints to be all lowercase. When IDs are involved, I like to use integers
and I like them to occur after the noun. Example: /user/1/place/2.

Request Methods

Even though PHP doesn’t do me any favors with $_PUT or $_DELETE I do prefer
to break up actions by request method. My logic behind doing so is because I’m
not a fan of including an “action” parameter to determine what we’re doing.
It keeps the requests a bit cleaner but at the sacrifice of being harder to
implement in some languages compared to just using $_GET and $_POST.

Responses

JSON for life, or at least something better comes around. I’ve worked with
enough XML to fill at least a dozen lifetimes and simply do not like working
with it.

Status Codes

I like em and I use them. In addition to leveraging the status code in the
header, I also put it in the response body (as part of the meta section).

Error Messages

In addition to the status code, I always include a message, even if the call
was successful. The message is part of the meta section of the response and
can be as terse as “OK” or as verbose as “The user profile was updated
successfully” or “The request was missing the %s parameter”. I am not a fan of
cryptic internal error codes and having to go dig back through documentation to
figure out what they mean, returning explanatory messages helps a ton.

Versioning

Versioning as a whole could probably be it’s own blog post at this point. I’m
a fan of having a major version number (integer) as part of the request,
/v1/user for example. By itself this is great for systems that receive
updates infrequently. HolidayAPI is a good example as the API
hasn’t been changed in at least a year or so.

Also, there is a school of thought that the version in the endpoint is not as
semantic as including it in the request header. I can definitely agree with
that but at a certain point it’s just nice to look at the endpoint and know
exactly what version you’re working with.

The major version number works for slow to update systems but can all fall
apart when you have an API that is hacked upon daily. The major version number
is still a good indicator of things, but you will probably need to include a
revision number of sorts to do further version abstraction. This gets even more
fun when you think in the scope of versioning the entire API or just the a
specific call.

Once you’ve made the decision, how do you handle that in your code? This is why
I enjoy these kinds of conversations, they can be as mind boggling as you want
them to be 🙂

Rate Limiting

Yes. Even if the rates are very high, you still need something in place to keep
abuse to a minimum. At this time I’m actually unsure if rate limiting should be
built into the service’s code or should be handled by the server with something
like this.

Code Reuse

Going back to versioning, I prefer that each version of my API does not rely on
any code from the previous version. If that means just copying over the entire
system as your first step so be it. It’s not pretty but it eliminates the
situation where a change in one version could impact the other version which is
not ideal at all.

To take this a step further, I like each of my endpoints to function without
anything from another endpoint. Same logic as above, I don’t want to run into
issues where the dependencies break other calls. Still not pretty but it gives
me piece of mind. I still have some reuse by way of using the same ORM and
utility functions, so it’s not a complete vacuum chamber.

This lack of reuse actually stemmed from how I started writing my unit tests
recently. With my tests, I didn’t want to have to run the entire harness just
to test a single class. Because of this, I started to write each test class to
provide 100% coverage for the entire file, instead of relying on tests in
another class to fill in the gaps. It results in more tests but it means if you
drop a class and tests from your system, the code coverage will not dip.

Responses

This is a fun one because I have a few assumptions and then some blank spots.
When adding a new record or editing the record, it’s a no brainer, you can
return the ID and/or the entire record (which could then be used as an echo).
What about when you delete a record? I’m actually still not sure on this one.
You could return the ID and/or the entire record just for consistency with
your other calls. You could also omit the response body entirely because you
deleted the record and there is nothing to give back. Another route would be to
include “deleted” in the response or perhaps in the “meta” section and continue
to leave the body blank. Only The Shadow knows.

I’ve already mentioned it, but I like to break my response into a “meta”
section and an optional “response” section. Meta contains everything that’s not
related to the data being returned. Including but not limited to the status
code, message, API version / revision, any echo token that was provided, et
cetera.

Conclusion

At the end of the day, the only truth for me is that there aren’t really any
wrong answers as long as your API performs well. If it doesn’t perform well at
the very least degrade gracefully. I run into a lot of APIs that simply can’t
handle a flood of calls and it cripples their entire API. A lot of times the
“flood” of calls I’m making is one every couple of seconds, far from an
onslaught 😉

Would love to continue this conversation down in the comments, feel free to
join in!

Josh Sherman - The Man, The Myth, The Avatar

About Josh

Husband. Father. Pug dad. Musician. Founder of Holiday API, Head of Engineering and Emoji Specialist at Mailshake, and author of the best damn Lorem Ipsum Library for PHP.


If you found this article helpful, please consider buying me a coffee.