Build APIs You Won't Hate: Everyone and their dog wants an API, so you should probably learn how to build them (2014)

2. Planning and Creating Endpoints

With your database planned and full of fake but useful data it is time to plan what your endpoints are going to look like. An endpoint is simply a URL. When you go to http://example.com/foo/bar then that is an endpoint and you simply need to call it /foo/bar because the domain will be the same for all of them.

The first step is to work out the requirements of an API, then we can move onto some theory and finally see the theory implemented in some examples.

2.1 Functional Requirements

Try thinking of everything your API will need to handle. This will initially be a list of CRUD (Create, Read, Update, Delete) endpoints for your resources, Talk to your mobile app developer, your JS frontend people, or just talk to yourself if you are the only developer on the project.

Definitely talk to your customers or “the business” (they are the customers) and get them to help you think of functionality too, but don’t expect them to know what an endpoint is.

When you have a relatively extensive list the next step is to make a simple list of “Actions”. This is very much like planning a PHP class, you first write up pseudo-code referencing the classes and methods like they exist, right? TDD (Test Driven Development)? If not that is how you should do it, or Chris Hartjes will find you, and he will kill you.

I will go ahead with the check-in application, introduced in the previous chapter, to show how these principles can be put in practice.

So if I have a “Places” resource in mind, I need to list out with just bullet points what it will do:

Places
Create
Read
Update
Delete

That is fairly obvious. Who will be able to view these places and who will be able to create and edit them is (for now) irrelevant in our planning stages, as this API will get much smarter with the ideas of user-context and permissions at a later date. For now just list all the things that need to be done.

A paginate-able list of places is also a requirement, so get that down:

Places
- Create
- Read
- Update
- Delete
List

The API will need to offer the ability to search places by location too, but that is not a brand new endpoint. If the API was built with SOAP or XML-RPC you would create a getPlacesByLatAndLon method to hit in the URL, but this isn’t SOAP - thankfully. The list method will handle that with a few parameters, so why not shove them in as a note for later:

Places
- Create
- Read
- Update
- Delete
- List (lat, lon, distance or box)

Adding a few parameters as a reminder at this stage is cool, but lets not worry about adding too much. For example, create and update are complicated so adding every single field would be a mess.

Update is more than just updating the specific “places” fields in the places SQL table. Update can do all sorts of cool stuff. If you need to “favorite” a place, just send is_favorite to that endpoint and you’ve favorited it. More on that later, just remember that not every single action requires its own endpoint.

Places will also need to have an image uploaded via the API. In this example we are only going to accept one image for a place and a new image overrides the old, so add “Image” to the list. Otherwise you’d add “Images” to the list:

Places
- Create
- Read
- Update
- Delete
- List (lat, lon, distance or box)
Image

A complete API “action plan” might look like this:

Categories
- Create
- List

Checkins
- Create
- Read
- Update
- Delete
- List
- Image

Opps
- Create
- Read
- Update
- Delete
- List
- Image
- Checkins

Places
- Create
- Read
- Update
- Delete
- List (lat, lon, distance or box)
- Image

Users
- Create
- Read
- Update
- Delete
- List (active, suspended)
- Image
- Favorites
- Checkins
- Followers

That might not contain everything, but it seems like a fairly solid start to our API. It is certainly going to take long enough to write all that so if somebody thinks of something else they can just make an issue.

Moving on.

2.2 Endpoint Theory

Turning this “Action Plan” into actual endpoints requires knowing a little theory on RESTful APIs and “best practices” for naming conventions. There are no right answers here, but some approaches have fewer cons than others. I will try to push you in the direction I have found to be most useful, and highlight the pros and cons of each.

GET Resources

·        GET /resources - Some paginated list of stuff, in some logical default order for that specific data.

·        GET /resources/X - Just entity X. That can be an ID, hash, slug, username, etc as long as it unique to one “resource”.

·        GET /resources/X,Y,Z - The client wants multiple things, so give them multiple things.

It can be hard to pick between sub-resource URLs or embedded data. Embedded data can be rather difficult to pull off so that will be saved for Chapter 7: Embedding Data. For now the answer is “just sub-resources”, but eventually the answer will be “both”. This is how sub-resources look:

·        GET /places/X/checkins - Find all the checkins for a specific place.

·        GET /users/X/checkins - Find all the checkins for a specific user.

·        GET /users/X/checkins/Y - Find a specific checkin for a specific user.

The latter is questionable, and not something I have ever personally done. At that point I would prefer to simply use /checkins/X.

warning

Auto-Increment is the Devil

In these examples X and Y can be an auto-incrementing ID as many developers will assume. One important factor with auto-incrementing ID’s is that anyone with access to your API will know exactly how many resources you have, which might not be a statistic you want your competitors to have.

Consumers could also write a script which hits /users/1, then /users/2 and /users/3, etc scraping all data as it goes. Sure they could probably do that from the “list” endpoints anyway, but not all resources should have a “get all” approach.

Instead a unique identifier is often a good idea. A universal unique identifier (UUID) seems like a logical thing to do: ramsey\uuid for PHPuuid for Rubyuuid in Python 2.5+.

DELETE Resources

Want to delete things? Easy:

·        DELETE /places/X - Delete a single place.

·        DELETE /places/X,Y,Z - Delete a bunch of places.

·        DELETE /places - This is a potentially dangerous endpoint that could be skipped, as it should delete all places.

·        DELETE /places/X/image - Delete the image for a place, or:

·        DELETE /places/X/images - If you chose to have multiple images this would remove all of them.

POST vs PUT: FIGHT!

What about creating and updating? This is where it gets almost religious. There are lots of people who will try to pair the HTTP POST or HTTP PUT verb (verb, i.e. a HTTP method) to a specific CRUD action and always only ever do that one action with that one verb. That sucks and is not productive or functionally scalable.

Generally speaking, PUT is used if you know the entire URL before hand and the action is idempotent. Idempotent is a fancy word for “can do it over and over again without causing different results”.

For example, create could be a PUT if you are creating one image for a place. If you were to do this:

1 PUT /places/1/image HTTP/1.1

2 Host: example.com

3 Content-Type: image/jpeg

That would be a perfect example of when to use a PUT because you already know the entire URL ( /places/1/image ) and you can do it time and time again.

At Kapture we use a POST to /checkins to create the meta-data for that new checkin, then that will return the URL for us to PUT the image to. You could try checking in multiple times and it wouldn’t matter because none of those processes would be complete, but POSTing multiple times is not idempotent because each checkin is different. PUT is idempotent because you are uploading that image to the full URL and you can do it over and over again if you like (for instance, because the upload failed and it has to try again).

So, if you have multiple images for places maybe you could POST /places/X/images and multiple attempts would be different images. If you know you are only going to have one image and a new attempt is an override then PUT /places/X/image would be ideal.

Another example could be user settings:

·        POST /me/settings - I would expect this to allow me to POST specific fields one at a time, not force me to send the entire body of settings.

·        PUT /me/settings - Send me ALL the settings.

It’s a tricky difference, but do not try and tie a HTTP Method to one CRUD action only.

Plural, Singular or Both?

Some developers decide to make all endpoints singular but I take issue with that. Given /user/1 and /user, which user is that last one returning? Is it “me”? What about /place? It returns multiple? Confusing.

I know it can be tempting to create /user/1 and /users because the two endpoints do different things, right? I started off down this route (#pun) originally, but in my experience this convention grows badly. Sure it works with the example of “users”, but what about those fun English words that create exceptions, like /opportunity/1 which becomes /opportunities. Gross.

I pick plural for everything as it is the most obvious:

·        /places - “If I run a GET on that I will get a collection of places”

·        /places/45 - “Pretty sure I am just talking about places 45”

·        /places/45,28 - “Ahh, places 45 and 28, got it”

Another solid reason for using plural consistently is that it allows for consistently named sub-resources:

·        /places

·        /places/45

·        /places/45/checkins

·        /places/45/checkins/91

·        /checkins/91

Consistency is key.

Verb or Noun?

Traditionally APIs would consist of a series of endpoints which all described actions:

1 POST /SendUserMessage HTTP/1.1

2 Host: example.com

3 Content-Type: application/x-www-form-urlencoded

5 id=5&message=Hello!

As you might have already gathered, this is not how things are done with REST.

Some API developers consider the following approach to be more RESTful because it uses a “sub-resource”:

1 POST /users/5/send-message HTTP/1.1

2 Host: example.com

3 Content-Type: application/json

5 { "message" : "Hello!" }

Nope, because that is still using a verb in the URL. A verb is an action - a doing term, and our API only needs one verb - the HTTP Method. All other verbs need to stay out of the URL.

A noun is a “place” or a “thing”. Resources are “things”, and a URL becomes the “place” on the Internet where a “thing” lives.

This example would be drastically more RESTful:

1 POST /users/5/messages HTTP/1.1

2 Host: example.com

3 Content-Type: application/json

5 { "message" : "Hello!" }

Perfect! We are creating a new message, which belongs to a user. The best part about keeping it nice and RESTful like this, is that other HTTP actions can be made to the identical URL:

·        GET /users/philsturgeon/messages

·        PATCH /users/philsturgeon/messages/xdWRwerG

·        DELETE /users/philsturgeon/messages/xdWRwerG

This is all much easier to document and much easier to understand for both humans and software which is “RESTfully aware.”

And, if like a client of mine you need to send multiple messages to multiple users - potentially hundreds of thousands - you could even make messages its own endpoint and send the messages in batches of a few hundred:

 1 POST /messages HTTP/1.1

 2 Host: example.com

 3 Content-Type: application/json

 4 

 5 {

 6      [{

 7             "user" : { "id" : 10 }

 8             "message" : "Hello!"

 9      },

10      {

11             "user" : { "username" : "philsturgeon" }

12             "message" : "Hello!"

13      }]

14 }

This would look incredibly similar to create the data as it would to retrieve the data, which is intentional.

2.3 Planning Endpoints

Controllers

You need to list events, venues, users and categories? Easy. One controller for each type of resource:

·        CategoriesController

·        EventsController

·        UsersController

·        VenuesController

Everything in REST is a resource, so each resource needs a controller.

Later on we will look at some things that are not resources. Sub-resources can sometimes just be a method, for example profile and settings are a sub-resource of “Users”, so maybe they can go in the “User” controller. These rules are flexible.

Routes

Try to avoid the temptation to screw around with magic routing conventions, just make them manually. I will keep going with the previous examples and show the process of turning the action plan into routes, using Laravel 4 syntax because why not:

Action

Endpoint

Route

Create

POST /users

Route::post('users', 'UsersController@create');

Read

GET /users/X

Route::get('users/{id}', 'UsersController@show');

Update

PUT /users/X

Route::put('users/{id}', 'UsersController@update');

Delete

DELETE /users/X

Route::delete('users/{id}', 'UsersController@delete');

List

GET /users

Route::get('users', 'UsersController@list');

Image

PUT /users/X/image

Route::put('users/{id}/image', 'UsersController@uploadImage');

Favorites

GET /users/X/favorites

Route::get('users/{id}/favorites', 'UsersController@favorites');

Checkins

GET /users/X/checkins

Route::get('users/{user_id}/checkins', 'CheckinsController@index');

There are a few things in here worth considering.

1.    Favorites go to the UserController, because favorites are only ever relevant to the user.

2.    Checkins go to the CheckinController, because we might already have a checkin controller handling /checkins and the logic is basically identical. We will know if there is a user_id param in the URL if our router is nice enough to let us know, so we can use that to make it user specific if needs be.

They are rather complex concerns, but are examples of things you can be thinking about at this point. You don’t want to have multiple endpoints doing painfully similar things with copy and paste logic because:

1.    PHP Copy/Paste Detector will be angry

2.    Your iPhone developer will be mad that different endpoints provide the same resource but in a slightly different format - therefore confusing RestKit

3.    It is boring and “ain’t nobody got time for that”

Methods

When you have listed all of the routes you will need for your application go and make them all as methods in their controllers. Make them all empty and have one of them return "Oh hai!"; and check the output. GET /places for example should Oh hai! in the browser.

You just wrote an API.