Create or Replace

I have seen the topic of “PUT” requests discussed a few different places, but never quite exactly in the context that I was looking for. I’ve seen “PUT” discussed in terms of “using PUT for a partial update”. I don’t really care about that, what I am interested in is more a “Create or Replace”.

For example, I have a partner using my API and they want to send some configuration data to my API. That configuration data may change over time in which case I want them to send me the new configuration data. I do not want to put the burden on them to keep track of if they need to use a POST (to create) or a PATCH (to update) that resource. So in my mind this seems like a perfect use case for using a PUT. The alternative would be having them always do a POST - in which case the server would have to do some lookups to find existing configuration to replace it or have them always do a PATCH - in which case I may have to create a “fake” configuration resource if it didn’t already exist.

I’m curious how other people are handling the “CREATE or REPLACE” scenario with jsonapi today without using PUT.

Thanks!

1 Like

Built into the client, if a HEAD …/{id} returns nothing (404) POST else PATCH.
PUT would be nicer.

Does a configuration belong to something?

Idea #1

Let’s say it belongs to a project. The client could do a POST /configurations and in the document, under the relationships field, make sure the project relationship is defined and points to the appropriate project. Internally, you handle that request by creating a new configuration and assigning it to the project.

Idea #2

Again, a configuration belongs to a project. You could make sure that any new project always has a configuration with default values. Therefore, there is always a configuration. The client needs to simply do a PATCH /projects/abc123/configuration or a PATCH /configurations/def456.

Idea #3

Nothing prevents you from pretending that all configurations (by that I mean all IDs) exist. If the client does a PUT /configurations/abc123 and abc123 does not exist in your database, simply pretend it already exists and “update” it with the appropriate values. You’re building an HTTP API, not a thin layer on top of your database.

Here’s what we ended up doing in my project. We only allowed PUTs to happen using a different (i.e., non JSON:API) part of the API, and then we disabled standard PATCH updates by explicitly returning 403s when that method is called on the endpoint. This keeps this part of the API compliant with the spec, but we do have to “do our own thing” to support idempotent PUTs.

It would be nice if there were a way to have PUT (when it does mean full replace) to be addressed in the spec. Idempotency is extremely powerful yet seems to be, in a sense, railroaded into a corner by the spec because some people have, in the past, abused PUTs.

I feel this thread derailed a little bit from the original topic.

If I got @mattjmorrison right, his main requirement as a create or update pattern:

For example, I have a partner using my API and they want to send some configuration data to my API. That configuration data may change over time in which case I want them to send me the new configuration data. I do not want to put the burden on them to keep track of if they need to use a POST (to create) or a PATCH (to update) that resource.

The main point seems to be that the client should not need to know if a resource already exists. Not that much if an update is a full-replacement or applies the changes to an existing resource.

This use case has a very specific prerequisite, which is not explicitly called out: It must use client-generated IDs. Only that allows the server to decide if the resource already exists or not.

Typically UUIDs are used as client-generated IDs. Doing so is also recommended by the JSON:API specification:

9.1.1 Client-Generated IDs

A server MAY accept a client-generated ID along with a request to create a resource. An ID MUST be specified with an id key, the value of which MUST be a universally unique identifier. The client SHOULD use a properly generated and formatted UUID as described in RFC 4122 [RFC4122].

JSON:API — Latest Specification (v1.1)

But the use case described by @mattjmorrison has some additional specifics: The client-generated IDs must be stable over time. And two independent clients must generate the same ID for representing the same entity. This is not supported using UUIDs.

The JSON:API specification mentions one exception in which it may be possible to use client-generated IDs, which are not UUIDs: importing data from another source.

NOTE: In some use-cases, such as importing data from another source, it may be possible to use something other than a UUID that is still guaranteed to be globally unique. Do not use anything other than a UUID unless you are 100% confident that the strategy you are using indeed generates globally unique identifiers.

In my experience this is a rare case. Therefore I’m not confident that it should be supported by the base specification itself. Implementing it as an extension seems to be the way to go.