GET or POST? Best practices for choosing the HTTP method

Last night, I read a not so old post from Brandon Savage about why "every developer should write their own framework". Very interesting reading, especially since I love writing my own frameworks. But I could not imagine how right he could be.
Yesterday, I decided to build a small library to write forms. Basically, I wanted the library to handle basic form stuff, like managing input types, select boxes, with a nice client and server-side validation. The server-side validation led me to think about one question: when the <form> is submitted, what should I use: HTTP GET or POST? And where should I submit the form?

In this article, I'll try to explain my thoughts, and the best practices that I deduced from coding my framework.

The Basics

But let's start by the basics first.
You all know that GET requests are visible in the browser while POST requests are "hidden" from the browser's address bar.

You should not choose to use POST to hide the parameters passed to your visitors. This is a bad practice.

There are many times where you would like to have a "clean" URL bar with a simple address, but this is not a reason to use POST. This will not prevent anyone to modify the request, and this is gives a false feeling of security.

Here is the right way to do things:
- Use GET when the request you are performing will not change the state of your application. This means your request will not write anything in the databaseor on the server side. Basically, if you perform 2 GET requests on the same URL, they should return the same result. This is called the "idempotent" property of the GET request.
- Use POST when the request you are performing will modify the state of your application. A POST request should write or update a record in database, or upload a file on your server, etc...

Cache systems and search engines are relying on these properties, so you should enforce them in your application. For instance, a cache proxy might try to cache the results of a GET request (if HTTP cache headers are set correctly), while it will not cache a POST request.

Achieving an optimal navigation experience

The GET/POST choice has a direct impact on the web experience of your users. For instance, if you try to refresh a page that was loaded with a POST, your browser will warn you that you are sending data again (with a somewhat hard to understand message). Furthermore, if the user tries to put a bookmark on a page that was loaded with a POST, he is likely to face problems.

For an optimal user experience we should aim for this behaviour:

  • When a user refreshes the page, he is never prompted by a warning telling him he will post the data again
  • When a user marks a page as a bookmark, he should be entitled (most of the time) to find the same page when he comes back

Here is a proposition to make this happen:

A POST request should end up with an HTTP redirect request that will load a new page with a GET method.

The idea is quite simple, if the application performs a redirect after the POST, the new page will be loaded using a GET request. This way, if the user clicks the "refresh" button, the GET page will be refreshed and there is no risk for the user to resubmit the page. Furthermore, the user can bookmark the page easily. It is true however that the redirect will incur an additional roundtrip between the browser and the server. In my experience, it is worth the benefit, so unless you are having extremely high latency issues, you should consider using this advice.

Additional tips

Usually, if you have a form that needs to be POSTed, you will certainly want to perform a validation on it. Of course, you cannot do it only in Javascript as it can be disabled. You need to perform the validation on the server. This leads to interesting considerations on where the action should lead...

Let's imagine a simple subscription page on a website. A typical PHP application would certainly implement it this way:

The subscription page would be served by "subscribe.php". Then, the form "action" would redirect to "subscribe_action.php" that would only take POST requests. If the form is validated we would then go to "success.php" through a redirect header. If validation failed, we would be redirected to "subscribe.php".

Redirecting to "subscribe.php" is costly enough (in term of development). Indeed, while redirecting, you must pass ALL parameters so that the form is displayed back with correct information.

Here is an alternative solution:

In this proposed solution, the POST request is performed on "subscribe.php". The subscribe page is having a different behaviour if we request it through a GET or a POST request. The great advantage is that if form validation is failed, we don't need to redirect! Indeed, the URL is already the right one. If the user tries to bookmark the page, it will succeed. If he presses the refresh button, we will be asked for confirmation, but that is only happening on a failed validation, so there is no risk of submitting twice the same form!

So now that I tried to develop my own framework, I understand best the decisions that lead developers to submit a POST on the same page that has served the GET request. This design pattern seems to be common enough. In particular, ASP.NET forms and events management is entirely based on this design-pattern, so my conclusions might be right:

By providing a different behaviour on the same URL when you use GET or POST, you can manage form validation in an easy way.

Does it make sense? What do you think? Personnaly, I'll rely on this behaviour on my next framework -- Raydee --, but I'll blog about this one later!