{{announcement.body}}
{{announcement.title}}

Call Me A Purist, But GET Still Should Not Change Data

DZone 's Guide to

Call Me A Purist, But GET Still Should Not Change Data

After a record-breaking month of handling code reviews, a Zone Leader finds developers misusing the GET HTTP request method and wanted to share his insight.

· Web Dev Zone ·
Free Resource

July was a busy month for me. From an hours-applied-to-projects perspective, July was a record-setting month for the year — with my utilization coming in right above 130% for the month. Two major releases were deployed in July, which was the primary cause of the higher-than-normal allocation of my time. Having an understanding spouse is an amazing thing, as she has been an advocate for these projects reaching their goals and objectives.

July was also the month where I processed more pull requests (PRs) than any month I can ever remember in my career. In my role at CleanSlate Technology Group, this extended outside my primary project and onto other projects as well.

One thing I found multiple times that caused concerns with me was the employment of an HTTP GET request method to do more than just retrieve data.

HTTP Request Methods

To recap, RESTful services can utilize the following HTTP request methods as part of the client/server communications:

  • GET — requests a representation of a requested resource and should only retrieve data.

  • HEAD — similar to GET, but without the response body.

  • POST — submits an entity to the specified resource, which can/often does cause a change in state on the server-side.

  • PUT — replace an entity at the specified resource.

  • PATCH — performs a partial modification to a specified resource.

  • DELETE — deletes the specified resource.

  • OPTIONS — provides available communication options at the target resource.

  • TRACE — provides loop-back tests along the path to a target resource.

  • CONNECT — establishes a tunnel to the server hosting the specified resource.

Reviewing these methods, the following application events translate to a corresponding request method:

  • Obtaining information for a list of data or a single record utilizes a GET request.

  • Submitting a new record from the application should utilize a POST request.

  • Making wholesale changes to an existing record should utilize a PUT request.

  • Changing a single attribute on a record (like when leaving the field) should utilize a PATCH request.

  • Hard-deleting a record from the application should utilize a DELETE request.

  • Soft-deleting a record from the application should employ a PUT or PATCH request.

For the PRs that I completed during July, I really wish the original developer had kept this information in mind when building out the design they asked me to review.

Using GET Incorrectly

In the round of PRs that I reviewed in July, I had three cases where a GET request was used to do something more than retrieve data:

  • Unsubscribe — the developer used a GET method, with a bunch of request parameters, to unsubscribe a user from a mailing list.

  • Performing an Action — the developer decided to introduce a verb-based /widget/{widgetId}/completeRequest  URI, which changed the state for a specified resource.

  • Secretive Request — the developer looked like they were performing a standard GET request, but a review of the underlying service layer showed that data was being changed programmatically each time the request was being made.

In all three of these PRs, I included a link to this thread on Stack Overflow. The thread, which is over six years old now, is something that I still have to refer to. To me, Oded's answer provides a great conclusion:

A GET is defined in this way in the HTTP protocol. It is supposed to be idempotent and safe.
As for why — a GET can be cached and in a browser, refreshed. Over and over and over.

In the examples above:

  • Unsubscribe — the user who used the unsubscribe GET URI would somehow be unsubscribed again and again - which doesn't make sense.

  • Performing an Action — the user who completed the request would be able to complete it over and over again.

  • Secretive Request — changing data every time might not reflect correct data, depending on caching layers in use.

The Action Processor Approach

Although I've known him for several years, I was fortunate to work with Ed Keen (@EdKeen) for about a year at a client engagement. Ed has been a software architect, whose design approaches are patterns I have employed on numerous occasions. One to note here is the concept of an Action Processor within a RESTful API.

In my "Processing in a RESTful World" January 2016 article, I provide an example of the Action Processor Approach that Ed introduced me to in 2014. In short, the approach utilizes a POST request for an Action class that contains the necessary attributes to perform some type of action on the server-side.

When completed, the HTTP Status code will reflect if the server had any issue processing the request, and the payload will return an ActionResponse class that provides details related to the processing itself.

So, while the HTTP Status code for the request returned a 202 Accepted, the information within the ActionResponse object could indicate there was an error processing the request.

Conclusion

I understand that employing a RESTful API is an easy way to provide server functionality for a number of client applications. I also understand that the primary design of REST doesn't always play well with all applications. However, I don't believe it is okay to decide how the HTTP request methods are going to be utilized.

While the Action Processor approach I have often employed is not without any challenges or issues, it is far better than using GET in the three ways I noted above.

I feel like we need to always hold the GET request method to a higher standard — always making sure it is safe, idempotent and never, ever, ever-changing data or the state of an application.

Have a really great day!

Topics:
failure, get request, http request, pull request, restful api, web dev

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}