Wednesday, August 27, 2014

How to create a Web API no one wants to use. Part 1: URI design

Introduction to this blog series


Later this year, Netflix will be closing their public APIs. Twitter and Google have already restricted their APIs. Despite prominent tech-companies making drastic changes to their APIs, the number of APIs keeps growing steadily. Suddenly, everyone has a Web API. Your car has one, Chuck Norris has one, and worst of all, tech-companies with no focus on quality whatsoever has one. And who are using these APIs? Most likely, no one.
This blog series will take you through the pitfalls of creating a Web API. From a developers perspective, what mistakes will result in no one wanting to use your API?

Part 1: URI Design
Part 2: HTTP Verbs
Part 3: HTTP Status Codes
Part 4: Result formatting
Part 5: Versioning
Exercises

URI design


If you're interested in creating a terrible Web API that will give developers headaches for ages to come and confuse them more for every piece of your API they use, you should start with your URI design. Badly named, inconsistent URIs that all violate the Principle of Least Astonishment will make your API just as disappointing as the movie "Mega Shark vs Giant Octopus".

Note: If you're building your API using hypermedia, these guidelines might not be applicable to you.

Example: U.S. City and County Web Data API

Starting out with an example, let's take a look at the "U.S. City and County Web Data API" created by The U.S. Small Business Administration.

Get all URLs they have registered for the city of Chicaco, Illinois:
GET http://api.sba.gov/geodata/all_links_for_city_of/chicago/il.json 200 OK

Assuming they use the same structure for all their endpoints, we can get all URLs they have registered for Cook Country, Illinois:
GET http://api.sba.gov/geodata/all_links_for_county_of/cook%20county/il.json 200 OK

So far, so good. What if I want to get all URLs they have registered for the state of Illinois? Based on their previous endpoints, this should work:
GET http://api.sba.gov/geodata/all_links_for_state_of/il.json 400 Bad Request

That didn't work. Which means that the developer using their API must head over to the documentation to figure out how it can be done, and the annoyance has already begun.

Problem: U.S. City and County Web Data API

The first thing to point out in this API is the terrible naming, which will at some point make this API difficult for the creators to maintain.

Using a structure such as all_links_for means you will end up with lots and lots of endpoints when the API grows. An example of this is already present in the API, where you should be able to retrieve not all links, but instead only primary links, which means all your endpoints will be duplicated:

http://api.sba.gov/geodata/all_links_for_city_of/...
http://api.sba.gov/geodata/all_links_for_county_of/...
http://api.sba.gov/geodata/primary_links_for_city_of/...
http://api.sba.gov/geodata/primary_links_for_county_of/...

Imagine what happens when they want to be able to give you broken links or secondary links, they will have to duplicate their endpoints for each new type of link, which in the end will be impossible to maintain.

The second problem with this URI structure is that it is not logical. As a developer, I am not able to predict what the URI of the next endpoint I'd like to use is based on my experience with the other endpoints.

Solution: U.S. City and County Web Data API

One of the many alternative ways this API could have been implemented. The pros of this implementation are:
  1. The URIs are readable and simple to understand
  2. The URIs are hackable, meaning I can remove the last part of the URI and still have a valid request.
  3. Querystring parameters are used for filters, so you don't have to duplicate all your endpoints when you'd like to add a new type of link.
Get all links for the state of Illinois:
/links/il

Get all links for Cook County, Illinois
/links/il/cook%20county

Get all links for Chicago, Illinois
/links/il/cook%20county/chicago

Get all primary links for Chicago, Illinois
/links/il/cook%20county/chicago?type=primary

Example: The World Bank API

Let's have a look at another Web API, The World Bank API. First, get all topics and indicators respectively:
GET http://api.worldbank.org/topics 200 OK
GET http://api.worldbank.org/indicators 200 OK

That looks like a pretty decent API, right? It's simple and it's logical. Now, let's get the topic with ID=5, and then get the indicators for this topic:
GET http://api.worldbank.org/topic/5 200 OK
GET http://api.worldbank.org/topic/5/indicators 404 Not Found

Ah, damn it. Logic no longer applies. In order to get all indicators for topic with ID=5, I can no longer use the plural name for indicator, so let's try with the singular name:
GET http://api.worldbank.org/topic/5/indicator 200 OK

Awesome, inconsistency!

Problem: The World Bank API

This API is not actually that bad, there are a lot worse of there. It follows many of the best practices out there, such as using nouns instead of verbs and having a logical structure.

However, the inconsistency between plural names (indicators) and singular names (indicator) strictly violates the Principle of Least Astonishment and makes the API inconsistent.

Solution: The World Bank API

Stick to plural nouns all the way in order to make the API consistent.

Get topics:
/topics

Get topic with ID=5:
/topics/5

Get indicators for topic with ID=5:
/topics/5/indicators

Summary


Badly named, inconsistent URI designs will not only be a pain in the arse for the developers using the Web APIs. They will also turn out to be a pain in the arse for the developers maintaining them. So do yourself a favour, and care about your API from the very first piece of code you write. 

3 comments:

  1. Highlighting the pain there.. Sadly inconsistent naming is far too common everywhere.

    Looking forward to the rest of the series!

    ReplyDelete
    Replies
    1. Thanks, Martin! I think we're all guilty of inconsitent naming at some point (at least I know I am), so there's definitely room for improvement!

      Delete