In the world of programming, consistency is one of the core principles allowing code to be readable. Consistency is often enforced through conventions, or more gently put, “best practices”.
At the highest level, the purpose of an API is to enable interaction between two or more applications.
This means that an API’s role is to allow the exchange of information, which is why web servers are commonly referred to as APIs - their main purpose is to expose endpoints for the consumption of other applications, _and yes, your browser also fits the role as an application, so even static websites aren’t exempt from this).
The aforementioned exchange of information can be broken down in resources, which are the “things” that you want to exchange with other applications, to put it bluntly.
Here’s a few examples: - users - articles - comments
Your APIs must leverage these resources, and the rest of this article will explain how.
It’s very easy to swept by programming standards telling you to create functions like getUser(id)
,
createUser(id, name)
, deleteUser(id)
or getUsers()
and apply those same standards to a REST API.
You might be tempted to use endpoints like those:
/createUser
/getUsers
/getUser?id=1
/updateUser?id=1&name=janedoe
/deleteUser?id=1
But when it comes to making an API, while it may appear perfectly fine, it isn’t.
HTTP has something specifically to make your life much easier and I’m sure you’ve heard of at least two of them before: GET, POST, PUT and DELETE.
They’re called HTTP methods, sometimes referred to as verbs: - POST is for creating a resource - GET is for reading a resource - PUT is for updating a resource - DELETE is for deleting a resource
Instead of using different names for the manipulation of a single resource, it’s significantly easier for developers to navigate your API if all your endpoints are consistent, and you really can’t get more consistent than using the exact same path but with different methods to differentiate each actions.
Using the name of the resource as endpoint will prevent other developers from wasting their time wondering if you used
/addUser
, /createUser
, /register
or even /registerUser
as endpoint.
Instead, it’ll just be:
POST /users
GET /users
GET /users/1
PUT /users/1
DELETE /users/1
Here’s a table to summarize:
Path / Method | GET | POST | PUT | DELETE |
---|---|---|---|---|
/users |
Retrieve a list of all users | Create a user | Delete all users | |
/users/1 |
Retrieve user 1 | Update user 1 | Delete user 1 | |
/users/1/books |
Retrieve user books | Create user book | Delete all of user 1’s books |
The resources exposed in your API should use the plural form (e.g. users
instead of user
)
Ignoring query string values, your URL must be all lowercase.
If one of your resources has multiple words, each word must be separated by a hyphen (e.g. private-messages
).
Finally, API versioning, although often overlooked, is key to creating an API that will withstand the test of time.
As grandiose as that sounds, it’s as simple as prefixing your endpoints with the current version of your API.
For instance, instead of using /api/users/1
, you’d use /api/v1/users
.
This is especially useful when your API is used by others, since it allows you to make breaking
changes to your API without the consumers of your API being affected (both /api/v1/users
and /api/v2/users
would be
available at the same time, thus allowing some time for your API’s consumers to migrate to the new version).
Obviously, versioning is not always a necessity. If your API has no consumers outside of yourself, there’s no
good reason to keep old code around. The best example I can think of right now in which API versioning is
absolutely necessary is an OAuth2 producer. If we take Google for example, there is an uncountable amount of
applications and websites that rely on Google’s Authorization Server (does Sign in with Google
ring a bell?).
Without API versioning, any breaking change made to the API would affect all those applications.