Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Express redirect POST to PUT with same URL

In my express app, I have two routes as follows:

router.post('/:date', (req, res) => {

    // if date exists, redirect to PUT
    // else add to database

})

router.put('/:date', (req, res) => {

    // update date

})

If date already exists on a POST call, I want to redirect to PUT. What is the best way to do this using res.redirect?

In the docs, all redirects are to different URL patterns. I would like to keep the URL same and change the rest verb from POST to PUT.

I had a look at this SO question, and added this line in POST:

res.redirect(303, '/:date');

but it did not redirect me to PUT.

like image 736
Akshay Khot Avatar asked Feb 25 '18 17:02

Akshay Khot


1 Answers

What you're trying to do here will not work for several reasons, but lickily you don't need to do any of that - see below.

First problem

The 303 "See Other" redirect that you're using here, by the spec should always be followed by a GET (or HEAD) request, not PUT or anything else. See RFC 7231, Section 6.4.4:

  • https://www.rfc-editor.org/rfc/rfc7231#section-6.4.4

The relevant part:

The 303 (See Other) status code indicates that the server is redirecting the user agent to a different resource, as indicated by a URI in the Location header field, which is intended to provide an indirect response to the original request. A user agent can perform a retrieval request targeting that URI (a GET or HEAD request if using HTTP), which might also be redirected, and present the eventual result as an answer to the original request. Note that the new URI in the Location header field is not considered equivalent to the effective request URI. [emphasis added]

Second problem

The other popular types of redirects - 301 "Moved Permanently" and 302 "Found" in practice usually work contrary to the spec as if they were 303 "See Other" and so a GET request is made.

See List of HTTP status codes on Wikipedia:

This is an example of industry practice contradicting the standard. The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302 with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 to distinguish between the two behaviours. However, some Web applications and frameworks use the 302 status code as if it were the 303. [emphasis added]

Third problem

There is a 307 Temporary Redirect (since HTTP/1.1) but it explicitly disallows changing of the HTTP method, so you can only redirect a POST to POST, a PUT to PUT etc. which can sometimes be useful but not in this case - see Wikipedia:

In this case, the request should be repeated with another URI; however, future requests should still use the original URI. In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request. For example, a POST request should be repeated using another POST request.

This 307 redirect is still not what you want and even if it was, it is not universally supported as far as I know so it needs to be used with caution.

See also this answer for more info:

  • Redirect POST to POST using Express JS routes

Your options

You can abstract away your controllers - which you will usually do anyway, for anything complex:

// controllers - usually 'required' from a different file 
const update = (req, res) = {
  // update date
};
const add = (req, res) => {
  if (date exists) {
    return update(req, res);
  }
  // add to database
};

router.post('/:date', add);
router.put('/:date', update);

Or you can abstract parts of your controllers as functions.

Universal controllers

Also, note that you can write universal controllers called for every HTTP method that might work here:

router.use('/:date', (req, res) => {
});

REST

Note that what you're doing here is not a usual RESTful way of naming your paths and it may make sense to use only PUT in your case for both new and updated dates.

Contrary to what many people think PUT doesn't mean UPDATE. It means to put a resource (new or not) to a certain URL (overwriting the old one if it already exists). It's pretty much like writing this in the shell:

echo abc > /the/path/to/file.txt

which will "update" the file if it exists, but it will also create a new file if it doesn't.

So for example, if you have /users/:id path, then you use:

  • GET /users to get a list of users
  • GET /users/:id to get a specific user with that ID
  • POST /user (not /users/:id) to create a new user without providing ID
  • PUT /users/:id to overwrite an existing user or to create a new user providing an ID
  • PATCH /users/:id to update the provided fields of user with that ID

Here, as I understand your :date is like an ID, ie. you want to overwrite the record if it already exists and create if if it doesn't exist. In both cases you are providing the :date path component so you might use PUT for all cases just as well.

In other words, you cannot redirect from one HTTP method to another HTTP method (except for GET) but you don't need to do it in this case.

like image 134
rsp Avatar answered Oct 18 '22 21:10

rsp