tl;dr
You should be fine with a single endpoint supporting PATCH, as long as you do it right.
The PATCH request payload is expected to contain a set of instructions for modifying the target resource. Suitable formats for representing this set of intructions are JSON Patch and JSON Merge Patch (and they allow modifications to nested values in your target JSON document).
Performing modifications to the state of a resource
To perform modifications to a resource, you could either use PUT or PATCH (or both). The difference between these methods, however, is reflected in the way the server processes the request payload to modify the target resource:
In a PUT request, the payload is a modified version of the resource stored on the server. And the client is requesting the stored version to be replaced with the new version. So it may not be entirely suitable for partial modifications.
In a PATCH request, the request payload contains a set of instructions describing how a resource currently stored on the server should be modified to produce a new version. It means it's the most suitable approach for performing partial modifications to a resource.
To make things clear, I put together some examples below.
Using PUT
Consider, for example, you are creating an API to manage contacts. On the server, you have a resource that can be represented with the following JSON document:
{
"id": 1,
"name": "John Appleseed",
"work": {
"title": "Engineer",
"company": "Acme"
},
"phones": [
{
"phone": "0000000000",
"type": "mobile"
}
]
}
Let’s say that John has been promoted to senior engineer and you want to keep you contact list updated. We could modify this resource using a PUT request, as shown below:
PUT /contacts/1 HTTP/1.1
Host: example.org
Content-Type: application/json
{
"id": 1,
"name": "John Appleseed",
"work": {
"title": "Senior Engineer",
"company": "Acme"
},
"phones": [
{
"phone": "0000000000",
"type": "mobile"
}
]
}
With PUT, the full representation of the new state of the resource must be sent to the server even when you need to modify a single field of a resource, which may not be desirable in some situations.
From the RFC 7231:
4.3.4. PUT
The PUT method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message payload. […]
Using PATCH
The PATCH method definition, however, doesn’t enforce any format for the request payload apart from mentioning that the request payload should contain a set of instructions describing how the resource will be modified and that set of instructions is identified by a media type (which defines how the PATCH should be applied by the server).
From the RFC 5789:
2. The PATCH Method
The PATCH method requests that a set of changes described in the request entity be applied to the resource identified by the Request-URI. The set of changes is represented in a format called a “patch document” identified by a media type. […]
Some suitable formats for describing such set of changes are listed below:
JSON Patch
It expresses a sequence of operations to be applied to a JSON document. It is defined in the RFC 6902 and is identified by the application/json-patch+json media type.
The JSON Patch document represents an array of objects and each object represents a single operation to be applied to the target JSON document.
The evaluation of a JSON Patch document begins against a target JSON document and the operations are applied sequentially in the order they appear in the array. Each operation in the sequence is applied to the target document and the resulting document becomes the target of the next operation. The evaluation continues until all operations are successfully applied or until an error condition is encountered.
The operation objects must have exactly one op member, whose value indicates the operation to perform:
add: Adds the value at the target location; if the value exists in the given location, it’s replaced.
remove: Removes the value at the target location.
replace: Replaces the value at the target location.
move: Removes the value at a specified location and adds it to the target location.
copy: Copies the value at a specified location to the target location.
test: Tests that a value at the target location is equal to a specified value.
Any other values are considered errors.
Having said that, a request to modify John’s job title could be:
PATCH /contacts/1 HTTP/1.1
Host: example.org
Content-Type: application/json-patch+json
[
{ "op": "replace", "path": "/work/title", "value": "Senior Engineer" }
]
JSON Merge Patch
It's a format that describes the changes to be made to a target JSON document using a syntax that closely mimics the document being modified. It is defined in the RFC 7396 and is identified by the application/merge-patch+json media type.
The server processing a JSON Merge Patch document determine the exact set of changes being requested by comparing the content of the provided patch against the current content of the target document:
- If the merge patch contains members that do not appear within the target document, those members are added.
- If the target does contain the member, the value is replaced.
null values in the merge patch indicate that existing values in the target document are to be removed.
- Other values in the target document will remain untouched.
With this, a request to modify John’s job title could be:
PATCH /contacts/1 HTTP/1.1
Host: example.org
Content-Type: application/merge-patch+json
{
"work": {
"title": "Senior Engineer"
}
}
The bottom line
When it comes to modifying the state of a resource, the usage of PUT and PATCH ultimately depends on your needs. Choose one, the other or both. But stick to the standards when supporting them:
If you choose to support PUT, the request payload must be a new representation of the resource (so the server will replace the state of the resource).
If you choose to support PATCH, use a standard format for the payload, such as JSON Patch or JSON Merge Patch (which contains a set of instructions that tell the server how to modify the state of the resource).
There are many libraries around to parse JSON Patch documents, so don't reinvent the wheel. If you think JSON Patch is too complex for your needs, then use JSON Merge Patch. If are confortable with both formats, then nothing stops you from supporting both formats.
And, as both formats allow you to modify nested values in your JSON document, you should be fine with a single endpoint for PATCH even when you have to allow modification in the nested values or your resource.
Validating the state of your resources
If you are concerned about how to keep things consistent after applying PATCH to your resources, I recommend having a look at this answer. In short, you are advised to decouple the models that represent your API resources from the models that represent your domain.
So, when handling a PATCH request:
- Fetch the domain model instance you intend to update.
- Convert the domain model to the correspondent API resource model.
- Apply the patch to the API resource model.
- Convert the API resource model with the updates back to the domain model.
- Validate the state of the domain model:
- If the state is valid, accept the request and persist the changes made to the domain model.
- Otherwise, refuse the request.
You may also want to refer to the RFC 5789 for details on error handling when processing PATCH request.
Further reading (specially if you use Java and Spring)
In case you use Java and Spring, you may want to have a look this post from my blog. If you don't use Java or Spring, the considerations around modifying resources with PUT or PATCH, in the first part of the post, may still interest you (although I nicely summarized the main idea in this answer).