Andre Soares Andre Soares - 3 months ago 9
reST (reStructuredText) Question

Should PUT always be able to insert?

Scenario:




  • Trying to create a RESTful web API (total compliance)

  • A PUT request is sent to
    /person/123
    (my intent is to update)

  • The resource
    /person/123
    does not exist



Doubt and sub-doubts:



Am I supposed to always allow the client to save (put) the resource, even if it does not exist (i.e. in the DB)?

Possibilities:


  1. No, it is appropriate to restrain the client save action in this case.


    • So, which status code would be appropriated for response (400, 404)?


  2. Yes. The API must allow the client to save using PUT.


    • So, how should I deal with the possibility of Bob saving some changes just after Alice deletes the same resource?



Answer

Before jumping on PUT, lets look at what makes PUT different from POST.

The expectation with POST is that it is non-idempotent. This means that calling POST multiple times with the exact same data may result in a different server state than calling it exactly once.

The expectation with PUT is that it is idempotent. This means that calling PUT multiple times with the exact same data should not result in a different server state than calling it exactly once.

Lets look at a couple examples of this:

POST Example

Imagine a POST request is sent to /person/add with some data to populate the new person being added. After the first call, the server will have:

[
    { person: 0, ...data... }
]

(JSON syntax used for brevity of example)

Now, imagine the exact same request happens another time or three:

[
    { person: 0, ...data... },
    { person: 1, ...data... },
    { person: 2, ...data... },
    { person: 3, ...data... }
]

In this example, POST is non-idempotent, and the same request resulted in different server states.

PUT Example

Now imagine a PUT request is sent to /person/123 with some data to update the person with an ID of 123. After the first call the server might have:

[
    { person: 0, ...data... },
    ...
    { person: 123, ...updated data... }
]

Now, imagine the exact same request happens another time or three:

[
    { person: 0, ...data... },
    ...
    { person: 123, ...updated data... }
]

In this example, PUT is idempotent, and the same request resulted in the same server states.

So what about when the person doesn't exist?

Well, there are two choices:

  • create the person with the given ID and populate its data
  • return a 404 as the given user was not found

Lets look at the difference in how the requests would be handled

Create person

Initial server state:

[
    { person: 0, ...data... }
]

After PUT /person/123 {...new data...}:

[
    { person: 0, ...data... },
    { person: 123, ...new data... }
]

After PUT /person/123 {...new data...} multiple times:

[
    { person: 0, ...data... },
    { person: 123, ...new data... }
]

This example shows that there's no real harm here to server state when you create the person from the data.

404 Error

Initial server state:

[
    { person: 0, ...data... }
]

After PUT /person/123 {...new data...}:

[
    { person: 0, ...data... }
]

After PUT /person/123 {...new data...} multiple times:

[
    { person: 0, ...data... }
]

This example also shows that there's no real harm to server state when you send an error if the person doesn't exist.

So what does this all mean?

REST isn't a strictly-defined specification that must be followed exactly. It's more like a set of guidelines.

What this means is that it's your call.

If creating a new data set from a PUT request can be done safely, and simplifies your program, there's no harm in allowing it to happen, so long as you do all the same validation and error handling that you'd be doing for a POST request to create a new object.

If creating a new data set from a PUT request is prohibitively difficult or can't be done safely—such as when only some of the data is being updated, and the server can't make a full object—then by all means disallow it and return the appropriate error code (404 Not Found).