When writing APIs these days, REST, short for representational state transfer, is considered the standard. And yet, REST itself isn’t actually a standard. This makes designing intuitive REST APIs tricky to get right. It is a way of thinking or an art form more than a checklist. Having shaped API standards at two companies now, I can tell you that there are a lot of things that go into creating a great API. There are some things that many companies get right and a lot that many get wrong. So how do I think about designing APIs? What makes a good API?
Consistency!
First and foremost, you’re creating an interface that someone else has to learn how to use. If you can keep the same paradigms throughout your interface, it is much easier to learn. Consistency extends from the big things, like what the resources even are, down to the little ones, like how you name things. On top of that, it’s useful to also be consistent with other APIs developers commonly use. If developers are already familiar with one API, then they will learn other similar APIs more quickly. Fortunately for all of us writing APIs, this desire for consistency means that we can create a checklist or standard. However, even with an extremely comprehensive standard, edge-cases and weird situations always arise. For all of these edge-cases, my response is to always go back to:
- What have we done before?
- If we’ve never done this or have been inconsistent, what do other APIs do? What is most common? Why do some companies depart from what is common (is it bad design or do they have a good reason)?
- What are likely/possible future use-cases that may need to do something similar? What makes sense for them?
- What do I find to be the most intuitive and least confusing? What do a handful of my coworkers think is the most straightforward?
When you first put a standard in place for your API, it’s almost guaranteed that you already have at least a handful of public APIs. Despite this, you should still write the standard for your ideal case. If you could rewrite all of your APIs today, what would they look like? After writing this standard for the ideal case, you will likely also need a standard for the current API that takes consistency into account and can act as a bridge between your current APIs and your ideal state. The next step is to figure out what you can do in terms of either versioning or slowly shifting to get your current version to that ideal version.
It’s All About Resources
Resources are one of the few things that REST does specify. REST APIs center around resources. The entire concept behind REST is that you’re exposing resources to the web. You have a set of resources, and you provide actions on those resources. These resources should be completely stable within your API. By stable, I mean that every single endpoint that returns a particular resource should return identical representations of that resource. It may be that some fields are blank, but the concept of that resource should always be the same. As a consumer of your API, I should have some stable concept of what a particular resource is, and I shouldn’t have to guess at what fields I’m going to get back from an endpoint. I should also be able to tell from the URL what type of resource I’m going to get back.
The only case where you might return something other than the complete resource is for relationships — for example, a resource that has a reference to another resource (say, it has a parent object). In this case, it is acceptable to have a minimal representation of the referenced resource. This is especially desirable in a multi-service architecture, where the service for the API may not own all of the other resources referenced and should, therefore, only be responsible for returning its own data. This minimal representation, whatever it contains, should contain at least the information needed to pull the complete resource (commonly that is two fields: type
and id
).
When designing these resources, keep in mind that your API resources DO NOT need to match the objects you have in your database. Think of it as your chance to redesign your data in a way that makes sense. Obviously, you also don’t want to put yourself in the position of having to do 20 complex database queries for each API call. However, where it makes sense, you can, and should, rename things and obfuscate confusing concepts. Just because you have three different database objects representing if a user was invited, confirmed, or archived, doesn’t mean that API consumers need to know.
If it seems like the different actions on a particular resource are trying to do different things, it may be that you’re conflating what should be multiple resources. While you don’t want everyone who calls your API to make many calls every time they try to do anything, you also want to make sure that you don’t conflate concepts. The more modular you can keep things, the more flexible your API will be for your users and the easier it will be for them to understand.
IDs and Types Make the World Go Round
Use unique IDs on your APIs. Always. I’m sure there is some use-case out there where unique IDs don’t work or make sense, but I have yet to come across it. I’ve also seen the opposite far too many times — where there is no ID and no good way to add one. All resources returned in a response should always include an ID. All GET endpoints should have an option to GET by ID. If you want to allow fetching a resource by name or something else, implement a query filter, but that doesn’t mean that you should skip the ID. In addition, this ID should be unique. It doesn’t have to be unique across all resources, but it should be unique across all objects of a given resource type (past, present, and future). If I delete one resource and try to fetch it later, I should never get a different resource. This ID doesn’t have to be a database ID — it could take several fields and combine or hash them together. It really doesn’t matter as long as it uniquely identifies an object.
Similarly, you should always return a type field with your resource. This may seem redundant — you called GET /flowers
, it seems obvious that the response would have "type": "flower"
. However, there are good reasons for returning the type. For any endpoint or field that could return more than one type, the type field identifies the type of each of those returned resources. Additionally, it enables the user to fetch the complete object (since you know what endpoint to use). As an example, you may have an approvers field on a change. These approvers could be either users or groups. The type allows you to distinguish between them. Including type also gives you future flexibility. Just because you don’t have groups right now doesn’t mean that you won’t add them in the future. Just because a field is always one type right now doesn’t mean that will always be true. Having the type present allows you to change that without it being a breaking change. If it wasn’t there, anyone writing code against your API would have assumed a type previously. If this type can no longer be inferred, any code with that assumption will break. It is the case that type is only helpful in some scenarios, and it can therefore be tempting to only use it for the cases where it’s needed. The counter-argument to that goes back to consistency and stable resources — your resources should always have the same fields. This also extends to type. It’s easier to always include type than to not have it when it could have made all the difference.
Resource Names are Important
As we said before, resources are the soul of your APIs. However, if no one knows what they represent, they can be almost useless. Names should accurately and clearly articulate what a resource represents. Additionally, they should ALWAYS be plural nouns. Resources are objects, so they should always be nouns because objects are nouns, not actions. But why plural? The way I think about it is that a resource endpoint doesn’t represent a single instance of the resource. Instead, it references the pool of all of that resource type on your server. For example, the endpoint /plants
represents all of the plants on the server. GET /plants
should return a list of those plants. POST /plants
adds an item to that pool of plants, so we’re adding something to plants. Even in the case of GET /plants/{plantId}
, which is expected to only ever return one item (because of the unique ID, remember?), we think of it as narrowing down that list of all plants to a specific one. It’s like saying that of all of those plants, limit it to the one with this ID. Therefore, resources should always be plural nouns. Likewise, I could do /plants?flower_color=blue
, which should return a list of all plants with blue flowers.
Follow Related Standards
While REST doesn’t have a standard, it does use some other protocols that do have standards. For example, most people write their REST APIs using JSON and HTTP. Neither of these is required, but both, especially HTTP, are nearly universal. Assuming you are using JSON and HTTP, you should adhere to their standards. The JSON standard is straightforward. The HTTP standard, meanwhile, is unsurprisingly quite lengthy. There’s a lot in there. There are few things, specifically, that I want to make sure to point out. Those are the correct use of status codes and the correct use of HTTP Methods. These are both complicated enough that I’ve written separate blogs for both status codes and HTTP Methods. Getting these right is essential for any good API.
Completeness
People often consider it to be the next level of REST or truly RESTful if you’ve implemented Hypermedia as the Engine of Application State (HATEAOS) links. That said, HATEAOS links, while cool in concept, are not useful for how most people consume modern APIs. Therefore, in almost all cases, I would recommend against implementing them. That said, I do still like the idea behind HATEAOS. To summarize here, the main idea of HATEAOS is to create a way to traverse the API without any prior knowledge of that API except a starting point. Each response contains links to related resources and so forth. Almost any reasonable person who is building against your API will be looking at your documentation. A lot. They will also be writing stable code that relies on specific, known endpoints, not randomly traversing things. Therefore, except for a couple of specific cases, HATEAOS links are not useful in practice.
However, while you might not build HATEAOS links, you should be sure that a user can traverse from resource to resource. They should be able to complete workflows through the API alone. This means that if I reference a parent folder in my file resource, I should also have a GET endpoint to fetch that parent folder. This is again why IDs are important. If I list groups associated with a user, I shouldn’t list just the group names; I should include the ID so that an API user can gain more information about or manipulate those groups. Even if you don’t know of a specific use case, you should enable a developer to string together results from an endpoint with any other resources related to that resource. They should be able to walk through the data from resource to resource, even if they’re not fetching the full links from you as a part of the response.
Most APIs contain some holes and lack some parity with their UI offerings, but it’s ideal to limit gaps where possible. Adding APIs can be time-consuming, so when making tradeoffs, I would consider making sure that the APIs available for a resource are complete rather than adding a single API or two across a bunch of resources. Part of the beauty of APIs is that they open up functionality possibilities never even dreamed of by the original company. They allow developers to extend beyond the use-cases provided in the UI and create entirely new flows. With that in mind, don’t just fill in the APIs for the use-cases you know about, but try to fill in as many APIs as possible, starting from the core objects.
Endpoint
With everyone talking about REST, but no actual standard, it can be challenging to write good, usable, and extensible APIs. It can take time and practice for design to come easily, but there are specific things that are important to keep in mind. Our APIs can be improved by taking the time to carefully think through resources — always using plural nouns, making sure to include type
and id
and following related standards. We can further enable the developers to use our API by providing APIs for as much of our product as possible. Finally, above all else, consistency across our API and with other APIs will make our APIs easy to use.
To Learn More About Building Great APIs
Building a great API is a journey. Here are a few links to help you along with yours:
- Nothing beats a quick, self-contained project for immediately applying concepts. Here are two you can jump right into: Building an API with Node.js, Express and TypeScript and Using Feature Flags in .NET Core Web API.
- Have a look at Split’s API docs, where we are along the journey to best practices ourselves.
- Take Split’s API out for a spin. Our API is now open to our free tier users, so, you can grab a free account and use Split’s API right now.
By the way, we’d love to have you follow us on YouTube and Twitter for more great content! You can also join the Split Community on Slack.
Get Split Certified
Split Arcade includes product explainer videos, clickable product tutorials, manipulatable code examples, and interactive challenges.
Switch It On With Split
The Split Feature Data Platform™ gives you the confidence to move fast without breaking things. Set up feature flags and safely deploy to production, controlling who sees which features and when. Connect every flag to contextual data, so you can know if your features are making things better or worse and act without hesitation. Effortlessly conduct feature experiments like A/B tests without slowing down. Whether you’re looking to increase your releases, to decrease your MTTR, or to ignite your dev team without burning them out–Split is both a feature management platform and partnership to revolutionize the way the work gets done. Switch on a free account today, schedule a demo, or contact us for further questions.