Web API Route Design for Non-CRUD Routes

Falafel Software Bloggers
Falafel Software
Published in
4 min readNov 28, 2016

Introduction

I’ve been finding myself thinking a lot about route design for the web APIs I’ve been building lately. For your basic CRUD (Create, Read, Update, Delete) controllers, everyone knows that the POST HTTP verb maps to create/insert; GET maps to read/select; PUT maps to update; and DELETE maps to — brace yourself — delete. But what about routes that represent things beyond these fundamental but basic data manipulations? I’ve done a bit of reading and thinking about the subject, and these are a distillation of the lessons I’ve learned.

URLs represent resources

I mean, it’s right in the name: Uniform Resource Locator. But what does that mean when designing the routes for an API? In my opinion, it means that routes should ideally only identify things — or to put it another way, they should ideally only contain nouns, using HTTP verbs to determine what action to take on the resource. This guideline is widely acknowledged, but frequently followed by the admission that it’s not always easy to do. For example, if you are defining a RESTful API to control services on a remote machine, this guideline states that you should not define the route with a verb

POST /services/{service}/pause
POST /services/{service}/run

But rather, you should treat the service’s state as a resource

PUT /services/{service}/state

With the state specified in the request body

{ "state": "paused" }

HTTP verb meanings

It seems pretty clear to me when to use GET and DELETE, but what’s the deal with POST and PUT? Say you want to design a route that sends a message to subscribers of a topic. Which verb is more appropriate? The answer to that question is founded on the concept of idempotency.

For the purposes of a RESTful API, an operation can be said to be idempotent if calling the same operation multiple time is succession has the same result as calling it a single time. Think about that in terms of CRUD operations: an INSERT operation is not idempotent; inserting values multiple times will result in multiple inserted values. In contrast, an UPDATE operation is idempotent; updating the same entity with the same values will have the same result whether you update the entity one time or one hundred times. In HTTP, POST operations are not expected to be idempotent, but PUT operations are. And that’s why POST is idiomatic for INSERT and PUT for UPDATE in CRUD applications.

Now consider the question posed at the beginning of this section: which verb is more appropriate to send a message to subscribers to a topic? If every call to the operation is expected to send another message, the operation is not idempotent, so POST is a more appropriate verb than PUT.

As an aside, it should be acknowledged that there are many more HTTP verbs than the well-known quartet of GET, PUT, POST, and DELETE. However, these four verbs are generally sufficient to fully express the concepts of the majority of API operations.

What goes where: Path, Query, and Body

When it comes to passing data in a request, there are many places where it can go. For GET requests, you have to decide what should go in the path and what should go into the query. For non-GET requests, the additional option to include data in the request body becomes available. What should go where? My guidelines are as follows:

  • Hierarchical data identifying the resource goes in the path
  • Flat, non-hierarchical data identifying the resource goes in the query. Optional parameters also go in the query.
  • What to do with the resource goes in the body

I think it’s a nice design goal to make every resource addressable entirely from the path, but it’s not a firm rule. The hierarchy represented in a URL need not necessarily be strictly unidirectional, either. In the case of a sales system with customers and orders, I think both of these following routes would make good sense and would not object to either based on the inversion of the relationships.

List orders of a customer

GET /customer/{customerId}/orders

Get the customer who placed a particular order

GET /order/{orderId}/customer

Summary

This is the sum of what I’ve learned from my internet research, thinking, and application of RESTful route design: That URLs should be nouns, that HTTP verb choices carry certain implications, and how to choose from the multiple options where request data should appear. Did I miss anything? What are your route design guidelines?

Originally published at Falafel Software Blog.

--

--