At SitePen, we have long been advocates for building web applications on a RESTful architecture. Over the last several years, it has been exciting to see organizations increasingly provide RESTful endpoints for the Dojo-based front-ends that we support and develop. A well-designed REST backend can be a excellent foundation for manageable, scalable applications, that will be ready to evolve into the future. I wanted to share a few tips for designing a set of RESTful services.
The central descriptor in any REST request is the URL. And consequently, defining the URLs, and their meanings is central to creating a REST API. The most important principle to remember in this process is that URLs represent resources, the conceptual entities that make up our application (whether they be contacts, friends, products, etc.). Often this is described as the “noun” or the object of a request. We can perform various types of actions (“verb”) on this resource, but the URL declares what the target of our action is, while the method (along with the request body for some methods) describe what the action will be. With this foundation, let’s first consider a few things to avoid in URLs:
- It is considered verboten to use URLs that describe the expected format of the response in the URL. URLs that end with
.xml, are improperly communicating the format in the URL. While this is expedient for simple files, for more developed resources, we can do better. The format of a response is part of the representation of a resource, and REST encourages multiple representations of each resource, giving a clear indication that we are still talking about the same resource, even if there are may be requests for the same data in JSON, XML, or some other format. HTTP makes this easy. We can include an
Acceptheader to clearly indicate the expected format, and even negotiate if the first choice is not available.
- Don’t indicate your server technology in your URLs. URLs that are suffixed with
.phpextensions clearly indicate the technology being used, and are irrelevant to what data is actually being retrieved or how the client will use it. If you decide to switch from PHP to Node.js in a few years, while trying to maintain backwards-compatibility for your clients, all those URLs with
.phpextensions will be either painful to change, or ugly to preserve.
- Try to avoid verbs, that is, an action to be taken, within a URL. Again, a URL indicates a resource, an entity or noun. The primary action to be taken should be described by the HTTP method. Now, of course, we may actually have more actions for a given URL/resource than corresponding HTTP methods. That’s fine. We can have multiple actions going through the POST method, with a parameter (whether it be JSON property, or a URL encoded query parameter) embedded within the request body, to distinguish different actions, without needing separate URLs.
In many data-driven applications, you will often need to create new objects which will need corresponding identifiers. In most applications, the server is responsible for generating unique ids for new objects. When this happens, we typically need to communicate the newly created object’s identifier to the client in case it needs to reference the object for future updates. Generally, this is done by making a POST request to create the new object (or row in the database table), and then the server can simply return the new object, with its identifier, in the HTTP response. Dojo’s
Cache store, as well as the new dstore‘s Rest store are designed to work with this convention, examining the response for any updates to the new object, including the identifier.
Another possibility is to actually generate identifiers on the client-side. This can be particularly beneficial in applications that need to support offline situations. When a server generates an identifier, a new object remains in an unidentified state until the server provides the identifier. This can be problematic if significant time may elapse before receiving the authoritative identifier, and additional modifications may take place. When a client generates the identifier, the new object can immediately be identified, there is no need to wait for server assignment. Remember that when a client is generating identifiers, it usually must be randomly generated. The client can’t usually coordinate incrementing ids and avoid conflicts with other clients without difficulty.
The purpose of REST is to provide infrastructure and patterns for better scalability, performance, and manageability of applications. However, this is a tool; application-specific practical concerns can always trump strict adherence to any specific pattern. There are a few frequent situations that often call for exceptions from strict interpretations of REST:
- JSONP – Many applications need to access data through cross-domain communication. Ideally, we would use cross-origin XHR (CORS) for this, but legacy browsers, as well as servers, often constrain us to use JSONP, which limits our ability to use headers to adhere to strict HTTP semantics.
- Multiple-Action/Transactions – Client applications may have multiple operation requests that they wish to send to the server. Sending these through separate HTTP requests may be unacceptably slow. In addition, we may even need multiple operations to be performed atomically in a single transaction. These concerns often require defining an endpoint that can handle a collection of operations.
- Session-Based Authentication – While HTTP has its own authentication mechanism, session-based authentication has proven much more practical for maintaining an application-specific branded login interface, and other aspects of maintaining authentication.
dojo/request , a collection of tools for managing I/O communication, includes a registry that makes it easy to create your own custom derivative of the XHR provider (or other providers) that include authentication tokens or other validation to facilitate authentication while protecting against cross-site request forgery attacks. This makes it easy to maintain fairly simple REST-style URLs in application code, while allowing the registry to negotiate transport-specific details.
A well designed REST interface can be provide a solid foundation for your application. Your application can take advantage of caching for better performance, discoverability for easier consumption of server services, and a consistent interface for managing and independently evolving client and server components. Dojo helps you to easily build on top of REST services, and use consistent resource-oriented APIs and strategies throughout your application.