0

I have a small side project I work on and off on, and I've taken the dive into Express, which I'm not familiar with, as a backend for it. For some reason, when I use my DELETE route instead of a POST, the body isn't passed.

api.js is my small fetch library I use to make the requests:

import store from '@/store';

// API headers
const HEADERS = {
  ALIAS: 'X-Organization-Alias',
  AUTH: 'Authorization',
  CACHE: 'Cache-Control',
  ACCEPT: 'Accept',
  CONTENT: 'Content-Type',
};

// HTTP codes
const CODES = {
  AUTH_REQUIRED: 401,
};

const api = {
  call: (method, url, input) => {
    let path = `${store.state.endpoint}/${url}`;
    return fetch(path, {
      method,

      // https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
      mode: 'cors',
      credentials: 'omit',

      headers: {
        [HEADERS.ALIAS]: 'web-client',
        [HEADERS.CACHE]: 'no-cache',
        [HEADERS.ACCEPT]: 'application/json',
        [HEADERS.CONTENT]: 'application/json',
      },

      body: method === 'POST' || method === 'PATCH' ? JSON.stringify(input) : null,
    })
      .then((response) => {
        if (!response.ok) {
          switch (response.status) {
            // Authorization requests do a hard refresh to the login page rather than route
            // This is so that any state is safely purged
            case CODES.AUTH_REQUIRED:
              document.location.href = '/login';
              break;
            default:
              console.log(response)
              // TODO create error dialog/toast
          }
          throw new Error();
        }
        return response;
      })
      .then((response) => response.json())
  },
  get: (url, input) => api.call('GET', url, input),
  post: (url, input) => api.call('POST', url, input),
  patch: (url, input) => api.call('PATCH', url, input),
  delete: (url, input) => api.call('DELETE', url, input)
}

export default api;

I make a request like:

  api.delete('arena/entry', { arena_history_id: arena_history_id })
    .then(response => {
      if (response) {
        this.saved_arenas = response;
      }
    })

And an empty object in req.body on the express route:

OPTIONS /arena/entry 200 2.435 ms - 20
{}
DELETE /arena/entry 200 2.488 ms - 29

If I change the request to:

  api.post('arena/entry', { arena_history_id: arena_history_id })
    .then(response => {
      if (response) {
        this.saved_arenas = response;
      }
    })

I correctly receive:

OPTIONS /arena/entry 200 2.353 ms - 20
{ arena_history_id: 13 }
POST /arena/entry 200 12.631 ms - 29

What in the heck is going on?

Daemios
  • 105
  • 5
  • See [Is an entity body allowed for a DELETE](https://stackoverflow.com/questions/299628/is-an-entity-body-allowed-for-an-http-delete-request) – jfriend00 Jun 10 '22 at 03:07

1 Answers1

1

DELETE request should not have bodies. From the recently updated HTTP spec:

Although request message framing is independent of the method used, content received in a DELETE request has no generally defined semantics, cannot alter the meaning or target of the request, and might lead some implementations to reject the request and close the connection because of its potential as a request smuggling attack (Section 11.2 of [HTTP/1.1]). A client SHOULD NOT generate content in a DELETE request unless it is made directly to an origin server that has previously indicated, in or out of band, that such a request has a purpose and will be adequately supported. An origin server SHOULD NOT rely on private agreements to receive content, since participants in HTTP communication are often unaware of intermediaries along the request chain.

Source: https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.5

So many servers and clients just drop the body, and that's within spec.

Evert
  • 93,428
  • 18
  • 118
  • 189
  • So the best practice would be to make a route called something like `arena/delete` but make it a POST? – Daemios Jun 10 '22 at 03:09
  • @Daemios I would recommend giving resources unique urls. The purpose of `DELETE` is to delete the thing located at the url. So if you're deleting some kind of 'arena' resource by some id, perhaps your uri should end in `/arena/123` – Evert Jun 10 '22 at 04:01
  • What if I need to support batch delete functionality, and don't want to hit url length limit? – MonstraG Apr 13 '23 at 13:30
  • @MonstraG well what are some options you can think of yourself? – Evert Apr 13 '23 at 15:05
  • @Evert, I went on googling spree myself, and ended up concluding that I would need to use PATCH to specific url like `arena/delete` with body – MonstraG Apr 18 '23 at 14:54
  • 1
    @MonstraG if you're going to have a 'verb endpoint' like '/delete', I would just use `POST` as it's more akin to an RPC call. You're not 'updating the delete endpoint' – Evert Apr 18 '23 at 16:52