CloudDays™ Quick Start – Designing Your RESTful API Part 3: Best Practices

imageIn the previous posts, you learned how to design your RESTful API. In this post, you will learn about the best practices of versioning, analytics, how to set up your API root, what your consumers are expecting for results,  why and how filtering should work, and caching.

Versioning

As you are building your API you will do your best o forsee the data needs of the consumers, those using your API, That said, your application and needs will change. Attributes will invariably be added and removed from your Resources.

Your API is a published contract between a Server and a Consumer. If you make changes to the Servers API and these changes break backwards compatibility, you will break things for your Consumer.

If you are simply ADDING new features to your API, such as new attributes on a Resource (which are not required and the Resource will function without), or if you are ADDING new Endpoints, you do not need to increment your API version number since these changes do not break backwards compatibility.

Over time you can deprecate old versions of the API. To deprecate a feature doesn’t mean to shut if off or diminish the quality of it, but to tell Consumers of your API that the older version will be removed on a specific date and that they should upgrade to a newer version.

In the case of the Microsoft Azure Management API, the Azure team has provided for versioning as part of the request header. In the case of Create Cloud Service, you are required to include the version number in the header.

x-ms-version Required. Specifies the version of the operation to use for this request. The value of this header must be set to 2010-10-28 or higher. For more information about versioning headers, see Service Management Versioning.

You could also use a version number in the resource URI. This may actually be easier for your third party developers.

Either way, you should provide a way to version your RESTful API to allow for changes and provide an easy transition for your users as your service grows and matures.

Analytics

Keep track of the version/endpoints of your API being used by Consumers. This can be as simple as incrementing an integer in a database each time a request is made. There are many reasons that keeping track of API Analytics is a good idea, for example, the most commonly used API calls should be made efficient.

API Root URI

The root location of your API is important. When a developer inherits an old project using your API and needs to build new features, they may not know about your service at all.

If your application is huge, or you anticipate it becoming huge, putting the API on its own subdomain (e.g. api.) is a good choice. This can allow for some more flexible scalability down the road.

If you anticipate your API will never grow to be that large, or you want a much simpler application setup (e.g. you want to host the website AND API from the same framework), placing your API beneath a URL segment at the root of the domain (e.g. /api/) works as well.

ASP.NET Web API provides this latter approach out of the box.

HTTPS

As a good RESTful API, you must host your API behind HTTPS.

Filtering

When a Consumer makes a request for a listing of objects, it is important that you give them a list of every single object matching the requested criteria. This list could be massive. But, it is important that you don’t perform any arbitrary limitations of the data. It is these arbitrary limits which make it hard for a third party developer to know what is going on. If they request a certain Collection, and iterate over the results, and they never see more than 100 items, it is now their job to figure out where this limit is coming from.

Thomas Hunter explains:

Minimize the arbitrary limits imposed on Third Party Developers.

Offer the ability for a Consumer to specify some sort of filtering/limitation of the results. The most important reason for this is that the network activity is minimal and the Consumer gets their results back as soon as possible. The second most important reason for this is the Consumer may be lazy, and if the Server can do filtering and pagination for them, all the better. The not-so-important reason (from the Consumers perspective), yet a great benefit for the Server, is that the request will be less resource heavy.

Filtering is mostly useful for performing GETs on Collections of resources. Since these are GET requests, filtering information should be passed via the URL. Here are some examples of the types of filtering you could conceivably add to your API:

?limit=10 Reduce the number of results returned to the Consumer (for Pagination)
?offset=10 Send sets of information to the Consumer (for Pagination)
?animal_type_id=1 Filter records which match the following condition (WHERE animal_type_id = 1)
?sortby=name&order=asc Sort the results based on the specified attribute (ORDER BY name ASC)

Expected Returns

When performing actions using the different HTTP verbs to Server endpoints, a Consumer needs to get some sort of information in return. This list is pretty typical of RESTful APIs:

  • GET /collection Return a listing (array) of Resource objects
  • GET /collection/resource Return an individual Resource object
  • POST /collection Return the newly created Resource object
  • PUT /collection/resource Return the complete Resource object
  • PATCH /collection/resource Return the complete Resource object
  • DELETE /collection/resource Return an empty document

Note that when a Consumer creates a Resource, they usually do not know the ID of the Resource being created (nor other attributes such as created and modified timestamps, if applicable). These additional attributes are returned with subsequent request, and of course as a response to the initial POST.

Caching

There are two main HTTP response headers for controlling caching behavior: Expires which specifies an absolute expiry time for a cached representation and Cache-Control which specifies a relative expiry time using the max-age directive.

Both work in combination with ETag (entity tag) which is an identifier, commonly a hash, that changes when the resource changes thereby invalidating cached versions or a Last-Modified header which is just the last modified date of the resource.

The request:

GET https://api.github.com/gists/1
Accept: application/json

had the reply:

200 OK
ETag: "2259b5bea67655550030acf98bad4184"

and a later request using

GET https://api.github.com/gists/1
Accept: application/json
If-None-Match: "2259b5bea67655550030acf98bad4184"

will return

304 Not Modified

without a response body. The same could be accomplished using Last-Modified/If-Modified-Since headers.

Resources

Special thanks to Thomas Hunter for his article on Principles of good RESTful API Design.

Also to Designing a RESTful Web API