How to design good APIs?

InterviewReady
4 min readJul 26, 2022

By- Avash Mitra

What is an API?

  • API stands for Application Programming Interface.
  • It is a documented way in which external consumers can understand how they can interact with your code. Or to put it simply it is just a function that external people can call.
  • It is very similar to a function. We define the parameters, return types, and possible errors.
  • NOTE: External consumers don’t need to know how your code works. API is just a contract

Example

We need a list of all followers of a Twitter user.

  • To do that the Follower service at Twitter is going to expose an API.
  • This API is just a function. Let’s assume that the function to get Twitter followers is:
function getFollowers (string userID){
...
return FollowersArray;
}
  • To get followers we will call this function with a string type of userID.
  • There are 2 possibilities of error.
  • The user does not exist
  • The user’s followers list is private
  • If there are no errors we will get a response which is an array of Followers

API design goals

  • Naming the API The name of the API should strictly describe its function. It should not do something more or less than the name suggests.

Consider the above example,

  • If the getFollowers function returns the list of followers as well as their tweets then the name getFollowers does not accurately describe the API. It should be named getFollowersandPosts
  • Parameters An API should accept parameters that are absolutely necessary to calculate the response, no more and no less.

Considering the Twitter example,

  • The API should only accept the userID from the client since only the userID is required to find followers. If we take fewer parameters then we cannot find followers. And if we take more then they are useless.
  • Note: In some cases where we need to optimize the query, we can take additional parameters.
  • Response The response object should only contain the data asked by the client. Stuffing additional data will have more network requirements. Also, it is confusing

Error Handling

  • In general, when you are defining errors, think about the common expectations and common responsibilities your API has. You don’t have to define every possible error but at the same time, you should not return a generic error for any error.

API Design in Practice

We usually expose our API on an HTTP endpoint. So first let’s understand how HTTP request and response objects work.

Suppose we have a function to get a list of admins from a group using a groupID.

So the HTTP request will be:

GET www.example.com/group/getAdmins/v1?groupID=133

  • GET — Method type (Other methods are POST, PUT, DELETE etc)
  • www.example.com — Address of your website (It is the routing).
  • group — It is the model that contains the function.
  • getAdmins — It is the function we need to call
  • v1 — It is the version of the function. (It is optional).

Since this is a GET method we don’t have a request object

The response object will be something like

{
"admin":[
{
"id":"12354",
"name":"user123"
},
{
"id":"12657",
"name":"user167"
},
...
]
}

It would help if you did not mix up routing with action

For example, you go to GET www.example.com/group but instead of defining the function you provide the groupID. This is going to be highly ambiguous. It does not know whether the groupID has to be fetched or the admins have to be fetched and so on.

Sometimes people put the action name in the request header. It is possible to do it this way but it is going to be a very dirty API.

Side effects

Suppose we have a function setAdmin(List<Users>,groupID) which takes a list of users and a groupID and makes the users admin.

Consider two exceptions

  • What happens if users are not members of this group? We can add this member to the group and then make the user admin.
  • What happens if there is no group In this case we can first make the group, add the members and make them an admin.

In these cases, there is a side effect. The function name is setAdmin but we are also setting members and sometimes setting groups.

  • Our API should have no side effects. We should break it into multiple APIs.
  • If we want to ensure atomicity there should be no side effects.
  • For example, if we call the setAdmin function and there is no group and it creates one. But just after it creates the group, it fails. What if someone else sets admins in the newly formed group.

In general, when we need atomicity for the action, we should call the right action instead of calling a generic action, because it pollutes the API.

Handling Large response

  • Pagination In this method we give the responsibility to the client. The client sends an offset with the API call. The server uses this offset to figure out which data it should send next.
  • Fragmentation This method is used when services are making API calls to other services in a microservice architecture. When there is a large response we can break the response into multiple pieces and send the pieces to the next service one by one. Each piece will have a number so the other service knows there is more information. Finally, there will be an end packet.

Consistency v/s Availability

  • If we have a lot of load on our database then we can serve more requests from a cache. If increases availability but since the data in the cache might be old, data might be inconsistent.
  • Also to reduce the load on our database we can degrade our service i.e., We can reduce the size of the response by sending fewer data.

That’s it for now!

You can check out more designs on our video course at InterviewReady.

--

--

InterviewReady

Simplifying interview preparation for software engineers