PUT and DELETE with HTML Forms in ExpressJS
In trying to send data from an HTML form to an ExpressJS backend I soon discovered two things:
FORM
elements only supportGET
andPOST
, notPUT
, norDELETE
;- ExpressJS does not have a built-in way to address this but fortunately there’s a middleware plugin, method-override, that does.
method-override
’s documentation is great and very much
on point, except for the FORM
post override section,
which I found a bit confusing on my first read (although in
honesty, now it seems much clear).
As a result, and for my own clarification, I decided to put together
as small example that showcases all the ways method-override
can be used from within HTML pages.
General approach
Middleware in ExpressJS typically takes place early in the
processing pipeline and as such method-override
will
attempt to identify a specific token in the received data,
per configuration, and will, well, override the method
aka HTTP verb used.
There are two main approaches we can use to trigger the app.put()
and app.delete()
route handlers from HTML code:
- Using AJAX
- Using the form’s
method="POST"
with a specific token.
Using AJAX
Almost all current versions of browsers support specifying an HTTP method.
// client code
var xhr = new XMLHttpRequest();
xhr.open('PUT', '/resource', true);
xhr.send();
// ---
// server code
app.put('/resource', function(req, res) {
console.log('PUT to /resource');
});
If you only work with modern browsers, there’s nothing more required.
However, if your front-end needs to be backward compatible with older
versions that don’t support HTTP methods (enterprise software developers,
I feel you), then method-override
can be configured to look for a token in the
headers being posted and override the method being used.
// server code
var methodOverride = require('method-override');
app.use(methodOverride('X-HTTP-Method-Override'));
app.put('/resource', ...
As such, the client code needs to specify the intended method in a header:
// client code
var xhr = new XMLHttpRequest();
xhr.open('POST', '/resource', true); // method-override needs it to be POST
xhr.setRequestHeader('X-HTTP-Method-Override', 'PUT');
xhr.send();
POST
-ing with specific token
If instead of AJAX, we intend to use the HTML’s FORM
element
to PUT
or DELETE
, method-override
can be configured to
look for a specific token either in the query string, or,
with a tiny bit more code, in the data being submitted.
Specific Token in Query String
In this example we will have the form POST
to
/resource?_method=PUT
and will configure method-override
to look for _method
in the query string and override
the HTTP method with the indicated verb.
// server code
app.use(methodOverride('_method'));
app.put('/resource', ...
On the client side, we’ll POST
to the above URL:
// client code
<form method="POST" action="/resource?_method=PUT">
...
</form>
Note: by default method-override
only examines POST
requests.
You can configure it to look at GET
requests:
// server code
app.use(methodOverride('_method', { methods: ['POST', 'GET'] });
but this is a really bad idea for two reasons:
GET
-ing/resource?_method=DELETE
is downright dangerous as anything unintentionally (or worse, intentionally) crawling your URLs will cause deletion of resources; this could be something as trivial as a browser pre-fetching links in order to speed up pages, or a browser extension investigating URLs for any number of reasons (phishing protection, status checking, statistics, etc).GET
-ing/resource?_method=PUT
with a payload makes no sense from an HTTP standard perspective. Payloads are forPOST
andPUT
.
So just don’t.
Specific Token in Form data
The second method involved sending the token with the POST
body.
The most common approach is to include a hidden field.
<form method="POST" action="/log" enctype="application/x-www-form-urlencoded">
<input type="hidden" name="_method" value="PUT">
<button type="submit">Submit</button>
</form>
The server-side code is a little bit more involved this time as it requires
another library: body-parser
, which you’re likely to use anyway if you
deal with form data or just HTTP body data in general.
body-parser
inteprets the incoming HTTP request body and makes it
available as key-value pairs in the body
property of the
request, req
, parameter.
method-override
allows one to specify a custom function to be called
during the middleware execution. In this custom function we will inspect
the request body, req.body
, for the presence of the desired token, _method
in this case, and return its value to the middleware that will in turn override
the request method.
// server code
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(methodOverride(function (req, res) {
if (req.body && typeof req.body === 'object' && '_method' in req.body) {
// look in urlencoded POST bodies and delete it
var method = req.body._method;
delete req.body._method;
return method;
}
}));
Composition
As a final note, it’s worth mentioning that you can have multiple
method-override
s in your middleware code, thus allowing the handling
of all the scenarios presented above.
Except for the GET
+DELETE
scenario. You should never do that.
// server code
var express = require('express');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var app = express();
app.use(methodOverride('X-HTTP-Method-Override'));
app.use(methodOverride('_method'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(methodOverride(function (req, res) {
if (req.body && typeof req.body === 'object' && '_method' in req.body) {
var method = req.body._method;
delete req.body._method;
return method;
}
}));
app.get('/resource', ...)
.get('/resource/:id', ...)
.post('/resource', ...)
.put('/resource/:id', ...)
.delete('/resource/:id', ...);
If you want to see all the examples above in action,
clone my method-override-example repo
and simply run npm start
then navigate to http://localhost:3000/
to play with each of these scenarios.