RESTful APIs tutorial: Learn key web service design principles
It's not hard to create a RESTful web service in Java. In fact, frameworks like Spring Boot, the Eclipse MicroProfile and Jakarta Enterprise Edition make the development of RESTful Java applications relatively easy.
But the problem with many RESTful web services isn't their development but their design. This RESTful APIs tutorial addresses the web service design issue and sheds light on the common mistakes software developers make when they conjure up a RESTful API.
A RESTful APIs tutorial
Designers have two key elements to consider in the development of a RESTful Java API:
- The URL pattern
- Which HTTP method to use
The first important principle this RESTful APIs tutorial asserts is that resources should always be accessed through URLs that uniquely identify them.
This should be a novel concept for anyone who has ever used a web browser. When we access a webpage or download a web-based PDF file, we point our browser to a URL that identifies that resource. The same concept applies when one accesses server-side resources with a RESTful Java web service. If a jQuery or Angular client needs to manipulate a resource, there should be a unique URL that the associated JavaScript code can use to identify the RESTful resource in question.
RESTful URLs example
I often prototype software development concepts with a little rock-paper-scissors application. In that application I typically code a component that keeps track of wins, losses and ties. An effective RESTful API that allows users to interact with the score would include the following URL in its design:
www.mcnz.com/rps/score
When users access this URL -- through the browser, through a RESTful JavaScript application or through a Spring Boot MVC component -- they'll receive a representation of the current score. A RESTful Java API that supports JSON might return a result such as this:
{ "wins":"5", "losses":"3", "ties": "0"}
If a RESTful JavaScript client is only interested in wins, the URL should follow a predictable format where wins is a subresource of the score:
www.mcnz.com/rps/score/wins → returns { "wins":"5"}
In fact, returning the number of wins in JSON format is probably overkill. Just returning the number of wins as basic text makes the result easily consumed by all clients, regardless of whether they can parse JSON or not. So the following would be preferable:
www.mcnz.com/rps/score/wins → returns "5"
Losses and ties should follow a similar RESTful URL format:
www.mcnz.com/rps/score/losses → returns "3"
www.mcnz.com/rps/score/ties → returns "0"
RESTful HTTP methods example
All of the RESTful API examples presented so far assume a simple GET invocation. The HTTP protocol provides a number of different methods to interact with a RESTful resource though a URL. When presented with a URL, servers generally assume it's a GET request. However, a RESTful API designer should consider at least three other HTTP methods -- namely, POST, PUT and DELETE.
Tenets of RESTful design
To tackle HTTP method, there are important RESTful design rules to follow. RESTful Java API designers tend to go astray if they violate these rules.
The first rule is that GET invocations can never change the state of any RESTful resource on the server. Our RESTful API fully complies with that rule.
RESTful PUT and DELETE methods
While not exactly a hard-and-fast rule, PUT and DELETE methods roughly map to the concept of save and delete. If designers want to delete a resource from the server, they should use the HTTP DELETE method. If they need a new resource created or need to update an existing resource, they should use the PUT method.
Idempotent methods
The PUT and DELETE methods are relatively straightforward for saving and deleting data. It's the other PUT and DELETE rule where RESTful Java API designers tend to run into trouble. This second rule demands idempotency from HTTP methods.
If something is idempotent, it can be done repeatedly, and the result will always be the same.
For example, imagine a client made a RESTful DELETE request to remove record 271. That call could be made once, or it could be made 100 times. The end result will always be the same -- so long, record 271. This scenario below is idempotent.
HTTP DELETE || www.mcnz.com/rps/score?record=271 #Good RESTful Java design
Contrast that with a request to delete the 10 oldest records in the database.
HTTP DELETE || www.mcnz.com/rps/score?oldRecordsToDelete=10 #Bad RESTful Java design
With this example, the RESTful URL would leave the database in a different state with each new invocation -- right up to the point where every record in the database is deleted. This method is not idempotent and thus violates a fundamental RESTful API principle.
PUT and RESTful API designed
The PUT method must also be idempotent. So, if one needs to change the number of wins from the current value in the database to 10, a good RESTful Java API would look like this:
HTTP PUT || www.mcnz.com/rps/score/wins?value=10
One could call this method over and over again, and after each invocation, the server would be left in the same state. The number of wins would be 10. This RESTful Java API is idempotent. Contrast that with a method that adds 10 wins each time it is invoked:
HTTP PUT || www.mcnz.com/rps/score/wins?add=10
This method is not idempotent because, with each invocation, the number of wins jumps to a new value. The number of wins might start at 10 the first time, 20 when invoked a second time and 30 the next. The final state of the resource is unpredictable with this method. It is not idempotent and not good RESTful API design.
Technically speaking, query parameters at the end of a URL should be used for just that: querying. In this example, we are using a query parameter to pass a payload to the server. Doing so makes the example simpler, but it also violates the spirit of a query parameter's intended use. In a future RESTful API design tutorial, we will demonstrate how passing a JSON string as part of the payload during a PUT invocation is a better design than using query parameters.
Flexible RESTful API design
So, we have seen in this RESTful APIs tutorial that the removal of the 10 oldest records from a database is a poor use of the DELETE method, and the incrementation of a number is a bad application of the PUT method. Does that mean we can't do these things with a RESTful API? No, not at all.
So far, this RESTful APIs tutorial establishes two very important rules:
- The GET invocation must not change the state of a resource.
- PUT and DELETE methods must be idempotent.
But notice that we haven't said anything about the POST method. One can use the POST method in any scenario that falls outside of the aforementioned rules. So, if you want to prune the 10 oldest records from the database, use the POST method. If you want to increment the number of wins by 10, then, again, use the POST method. The POST method is the Swiss army knife of RESTful design.
HTTP POST || www.mcnz.com/rps/score/wins?add=10
HTTP POST || www.mcnz.com/rps/score?oldRecordsToDelete=10
Of course, there is a danger if you approach the POST method as a cure-all for every challenging corner case your RESTful API encounters. Just because you don't violate rules on idempotency or misuse the GET, PUT and DELETE methods, it doesn't mean you have properly designed a RESTful API. Overuse of the POST method is itself RESTful anti-pattern.
RESTful design anti-patterns
Far too often, you will see a supposedly RESTful system in which the designers have shoehorned every permutation of their API into a POST invocation. Just because you haven't violated important RESTful principles doesn't mean you have developed an effective RESTful API. The tendency toward heavy POST method use often occurs when RESTful API designers take a service-based approach to their problem domain. When you create RESTful APIs, it's important to always take a resource-based approach to your system.
There is much to learn, such as best practices for passing payload data to the server, how to structure URLs to identify resources and how to avoid the trap of taking a service-based approach to a resource-based design. We will cover those in subsequent RESTful APIs tutorials. But a strong foundation in structuring URLs and the proper use of HTTP methods is the basis of every beautiful resource-based API ever designed.