A Brief History of HTTP
Before we get into PATCH, let’s take a quick trip down memory lane with HTTP (HyperText Transfer Protocol). Think of HTTP as the language that web browsers and servers use to chat. It all started with:
- HTTP/0.9 (1991): The OG version, super simple, just GET requests.
- HTTP/1.0 (1996): Introduced in RFC 1945. Added more methods, status codes, and headers.
- HTTP/1.1 (1997): Defined in RFC 2068 and later updated in RFC 2616. Brought persistent connections and chunked transfers.
- HTTP/2 (2015): Enhanced performance with multiplexing and header compression.
- HTTP/3 (2020): The latest and greatest, using QUIC for faster connections.
Enter the PATCH Method
Now, let’s talk about PATCH. No, it’s not a software update or a pirate’s accessory. The PATCH method was introduced in RFC 5789 in 2010.
Its purpose? To allow partial updates to a resource.
Imagine you have a document, and you only want to change one sentence.
Instead of sending the whole document back and forth (like with PUT), PATCH lets you send just the changes. Efficient, right?
PATCH vs. PUT
You might be thinking, “Wait, doesn’t PUT handle updates?” Yes, but there’s a twist:
- PUT: Replaces the entire resource with the data you send. It’s like redecorating your entire house because you bought a new couch.
- PATCH: Updates only the specified parts of the resource. It’s like just swapping out the old couch for the new one.
π Server-Side PATCH Implementation
This is how you handle incoming PATCH requests on the server.
πΉ C# Server (ASP.NET Core)
π Install dependencies first
1
| dotnet add package Microsoft.AspNetCore.JsonPatch
|
π C# Server Implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.JsonPatch;
using System.Collections.Generic;
using System.Linq;
[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
private static List<User> Users = new List<User>
{
new User { Id = 1, Name = "Alice", Email = "alice@example.com" },
new User { Id = 2, Name = "Bob", Email = "bob@example.com" }
};
[HttpPatch("{id}")]
public IActionResult PatchUser(int id, [FromBody] JsonPatchDocument<User> patchDoc)
{
var user = Users.FirstOrDefault(u => u.Id == id);
if (user == null) return NotFound();
patchDoc.ApplyTo(user, ModelState);
if (!ModelState.IsValid) return BadRequest(ModelState);
return Ok(user);
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
|
π‘ Whatβs Happening?
- We create an in-memory list of users.
- The
PATCH
method finds the user, applies the changes using JsonPatchDocument
, and returns the updated user.
π Python Server (Flask)
π Install dependencies
1
| pip install flask jsonpatch
|
π Python Server Implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| from flask import Flask, request, jsonify
from jsonpatch import JsonPatch
app = Flask(__name__)
users = {
1: {"id": 1, "name": "Alice", "email": "alice@example.com"},
2: {"id": 2, "name": "Bob", "email": "bob@example.com"},
}
@app.route('/api/users/<int:user_id>', methods=['PATCH'])
def patch_user(user_id):
if user_id not in users:
return jsonify({"error": "User not found"}), 404
patch_data = request.get_json()
patch = JsonPatch(patch_data)
users[user_id] = patch.apply(users[user_id])
return jsonify(users[user_id])
if __name__ == '__main__':
app.run(debug=True)
|
π‘ Whatβs Happening?
- The
PATCH
method looks for the user, applies changes using JsonPatch
, and updates the user.
𦫠Go Server (Gin)
π Install dependencies
1
2
| go get -u github.com/gin-gonic/gin
go get -u github.com/evanphx/json-patch/v5
|
π Go Server Implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/evanphx/json-patch/v5"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
var users = map[int]*User{
1: {ID: 1, Name: "Alice", Email: "alice@example.com"},
2: {ID: 2, Name: "Bob", Email: "bob@example.com"},
}
func PatchUser(c *gin.Context) {
id := c.Param("id")
var patchData []byte
if err := c.BindJSON(&patchData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON"})
return
}
user, exists := users[id]
if !exists {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
userData, _ := json.Marshal(user)
patchedData, err := jsonpatch.MergePatch(userData, patchData)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid PATCH data"})
return
}
json.Unmarshal(patchedData, &user)
users[id] = user
c.JSON(http.StatusOK, user)
}
func main() {
r := gin.Default()
r.PATCH("/api/users/:id", PatchUser)
r.Run(":8080")
}
|
π‘ Whatβs Happening?
- It binds the JSON Patch request, merges it with the existing user data, and updates the record.
π» Client-Side PATCH Implementation
This is how you send PATCH requests from a client.
πΉ C# Client (Sending a PATCH request)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
class Program
{
static async Task Main()
{
var patchData = new[]
{
new { op = "replace", path = "/email", value = "new.email@example.com" }
};
var content = new StringContent(JsonConvert.SerializeObject(patchData), Encoding.UTF8, "application/json-patch+json");
using (var client = new HttpClient())
{
var response = await client.PatchAsync("http://localhost:5000/api/users/1", content);
var responseText = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseText);
}
}
}
|
π Python Client (Sending a PATCH request)
1
2
3
4
5
6
7
8
9
10
11
12
13
| import requests
import json
url = "http://localhost:5000/api/users/1"
headers = {"Content-Type": "application/json-patch+json"}
patch_data = [
{"op": "replace", "path": "/email", "value": "new.email@example.com"}
]
response = requests.patch(url, headers=headers, data=json.dumps(patch_data))
print(response.json())
|
𦫠Go Client (Sending a PATCH request)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "http://localhost:8080/api/users/1"
patchData := []map[string]string{
{"op": "replace", "path": "/email", "value": "new.email@example.com"},
}
jsonData, _ := json.Marshal(patchData)
req, _ := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json-patch+json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Status:", resp.Status)
}
|
π― Conclusion
- The PATCH method is useful for partial updates without resending the whole resource.
- We saw how to handle PATCH requests on the server in C#, Python, and Go.
- We also sent PATCH requests from a client in C#, Python, and Go.
π Next time someone asks, “Why use PATCH?” you can confidently reply, “Because sending only whatβs needed saves bandwidth and resources!” π
π Key Ideas Table
Concept | Explanation |
---|
PATCH vs. PUT | PATCH updates only part of a resource; PUT replaces the whole thing. |
C# Server | Uses JsonPatchDocument to apply changes. |
Python Server | Uses Flask and jsonpatch to modify resources. |
Go Server | Uses Gin framework and jsonpatch for updates. |
Client Requests | PATCH requests are sent using HTTP libraries in C#, Python, and Go. |
π References