Featured image of post Implementing HTTP PATCH in C#, Python and Go

Implementing HTTP PATCH in C#, Python and Go

For efficient updates

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

ConceptExplanation
PATCH vs. PUTPATCH updates only part of a resource; PUT replaces the whole thing.
C# ServerUses JsonPatchDocument to apply changes.
Python ServerUses Flask and jsonpatch to modify resources.
Go ServerUses Gin framework and jsonpatch for updates.
Client RequestsPATCH requests are sent using HTTP libraries in C#, Python, and Go.

πŸ“š References