We’ve built several REST APIs. Good API design falls into two categories (1) Clearly following good object oriented design principles and (2) clearly following the HTTP 1.1 specification.
By viewing antipatterns, examples of what not to do, you can come away with a good sense of what should be done, and I’ll even give a few good API Design examples later.
Let’s take the basis of our antipattern URI. The example links do not resolve.
The path of your API should clearly delineate a hierarchical relationship between resources and objects.
In this example /api is not a resource. It’s better to use a subdomain for api scope, a la:
The object “jim_carnes” should be hyphenated not underscored because underscores provide legibility concerns with underlines in text. The trailing slash “/” at the end of the URI should be completely removed. In HTTP protocol trailing slashes represent a distinct and separate resource than a non-trailing slash. Collection names should always be pluralized and object names should always be singularized. The above example breaks both rules as the collection “user” is singular and the object “sports” is plural.
A better design:
Controller actions that correspond with CRUD operations (Create, Read, Update, Delete) should always be handled with HTTP 1.1 protocol behavior and in the event that a controller action cannot be scoped to a CRUD operation it should be pluralized. CRUD function names should never be used in URIs. Here is an antipattern:
This URI is using the query portion of the URI to provide parameters for the delete action. The purpose of query parameters is to handle filtering and pagination because URIs should be shared and results based lists cannot be shared transparently without including filtering and pagination within the URI. A better way to set up this URI is to issue an HTTP DELETE method to the users collection with the name as a parameter in the request body.
Here is an example of a request that would properly support this setup
curl -xdelete -v http://www.api.mysite.com/users -d "name=jim-carnes"
Proper mapping on HTTP verbs to URIs can be found all over the place, but here’s the short list for a users collection:
- Create: POST /users
- Read: GET /users/:id
- Update: PUT /users/:id
- Delete: DELETE /users/:id
If and only if you have a collection action that falls outside of the scope of the above should you define your own URI endpoint. In EVERY case the URI endpoint should be a verb and follow the object in the URI path. Common examples of such actions are unsubscribe and subscribe. For example: http://www.api.mysite.com/8081/subscribe is a perfectly fine URI. Only use controller resources to map to actions that cannot be logically mapped to one of the standard methods.
It should be mentioned the DELETE HTTP verb should only be used when an actual DELETE is being processed. In other words a GET request to the same collection with the /:id should return no objects immediately following a DELETE.
Your API should support HEAD and OPTIONS HTTP verbs. HEAD should return only headers with an empty response body and OPTIONS should return the Allow header with all available verbs for that resource, i.e. “Allow: GET, POST” and an optional response body.
Lastly, any request to a custom controller action such as /subscribe or /unsubscribe from above should ALWAYS use a POST request.
Uppercase letters do not matter in the host (http://) and domain (www.mysite.com) portions of a URI (HTTP Protocol is not case sensitive).
However, capitalization does matter for the path.
The above example is a different URI resource than this example:
The server side implementation could error this resource. When designing your API always use lowercase letters for resources, objects, and actions. The lookup will be much easier to deal with and more transparent for developers consuming your API.
Do not include file extensions in URIs. The above file extension should be made clear through the request content-type header, such as “Content-Type: application/json”. Supporting including file extensions in URIs creates dependencies on the request. For example an API that can respond to XML or JSON now needs two URIs instead of one. The URI should be kept clean and the content type determined in the request headers.
http://www.api.mysite.com/user/jim-carnes/documents/1 should be able to return any content type that is accepted within the “Content-Type” response header. If it is not accepted the API should return a specific header status. I will do a follow up post on proper use of headers and header status codes.
Examples of proper REST API Design patterns
curl -XDELETE -v http://www.api.mysite.com/users -d "name=jim-carnes"