JSON Patch no Node.js aplica alterações parciais a documentos JSON por meio de uma lista de operações padronizadas. O formato definido pelo RFC 6902 usa add, remove, replace, move, copy e test.
Ele é útil quando clientes precisam alterar poucos campos sem reenviar o recurso inteiro. Porém, aceitar paths arbitrários pode permitir mudanças indevidas, escalada de privilégios, documentos inválidos e ataques de consumo de CPU. A aplicação precisa validar operações, paths, tamanho e resultado final.
Content-Type
PATCH /users/123
Content-Type: application/json-patch+jsonValide o tipo antes do parsing. O corpo é um array de operações.
[
{ "op": "replace", "path": "/displayName", "value": "Leandro" },
{ "op": "add", "path": "/preferences/theme", "value": "dark" }
]JSON Pointer
O campo path usa JSON Pointer. Barras separam segmentos, ~1 representa / e ~0 representa ~.
/address/city
/items/0/name
/metadata/a~1bNão trate o path como uma string de propriedade simples.
Operação add
{ "op": "add", "path": "/tags/-", "value": "nodejs" }O hífen adiciona ao final de um array. Limite tamanho do array e valide o valor.
Operação remove
{ "op": "remove", "path": "/temporaryNote" }Remover campos obrigatórios deve falhar na validação final.
Operação replace
{ "op": "replace", "path": "/email", "value": "novo@example.com" }O path precisa existir. Alterações sensíveis, como e-mail ou função, podem exigir fluxos próprios e não devem ser liberadas apenas porque o patch é válido.
Operação test
{ "op": "test", "path": "/version", "value": 17 }test permite concorrência otimista dentro do documento. Se o valor mudou, o patch falha antes de aplicar as operações seguintes.
move e copy
{
"op": "move",
"from": "/draftTitle",
"path": "/title"
}Essas operações aumentam a superfície de validação. Muitas APIs podem começar aceitando apenas add, remove, replace e test.
Validação inicial
const allowedOps = new Set(['add', 'remove', 'replace', 'test']);
function validatePatch(patch) {
if (!Array.isArray(patch)) {
throw new Error('Patch deve ser um array');
}
if (patch.length === 0 || patch.length > 50) {
throw new Error('Quantidade de operações inválida');
}
for (const operation of patch) {
if (!operation || typeof operation !== 'object') {
throw new Error('Operação inválida');
}
if (!allowedOps.has(operation.op)) {
throw new Error('Operação não permitida');
}
if (typeof operation.path !== 'string' || operation.path.length > 200) {
throw new Error('Path inválido');
}
}
}Allowlist de paths
Não permita alterar qualquer campo.
const allowedPaths = new Set([
'/displayName',
'/preferences/theme',
'/preferences/language'
]);
for (const operation of patch) {
if (!allowedPaths.has(operation.path)) {
throw new Error(`Path não permitido: ${operation.path}`);
}
}Paths de arrays ou objetos dinâmicos podem exigir regras por prefixo, sempre com limites.
Autorização por campo
function authorizePatch(user, operation) {
if (operation.path === '/role' && !user.permissions.includes('user.role.write')) {
throw new Error('Operação não autorizada');
}
}Validar o recurso não significa que o usuário pode alterar todos os campos. Verifique cada operação antes de aplicar.
Prototype Pollution
Bloqueie segmentos perigosos como __proto__, prototype e constructor, especialmente se a biblioteca aplica paths em objetos JavaScript comuns.
const forbidden = new Set(['__proto__', 'prototype', 'constructor']);
function assertSafePointer(path) {
const segments = decodeJsonPointer(path);
if (segments.some(segment => forbidden.has(segment))) {
throw new Error('Path inseguro');
}
}Consulte Prototype Pollution no Node.js.
Aplicação atômica
O RFC define que as operações são aplicadas em sequência. Se uma falha, o patch inteiro deve ser rejeitado. Trabalhe sobre uma cópia e só persista o resultado final.
const current = await repository.findById(id);
const candidate = structuredClone(current);
applyPatch(candidate, patch);
validateResource(candidate);
await repository.save(candidate);Escolha uma biblioteca bem mantida e teste o comportamento de erro e mutação.
Validação do recurso final
Depois de aplicar, valide o documento completo com schema.
const valid = validateUser(candidate);
if (!valid) {
throw new Error('Patch produz recurso inválido');
}Veja JSON Schema no Node.js.
Concorrência com ETag
Combine PATCH com If-Match.
PATCH /users/123
If-Match: "user-17"A aplicação só salva se a versão atual corresponde.
UPDATE users
SET document = $3::jsonb,
version = version + 1
WHERE id = $1 AND version = $2;Se nenhuma linha foi alterada, responda 412 Precondition Failed ou conflito conforme o contrato.
Consulte ETag no Node.js e Optimistic Locking no Node.js.
JSONB no PostgreSQL
Para poucos paths conhecidos, pode ser possível traduzir o patch para operações jsonb_set. Para patches arbitrários, aplicar no Node.js, validar e salvar com versão costuma ser mais simples.
Veja JSONB PostgreSQL no Node.js.
Arrays e índices
Índices mudam quando elementos são removidos. Um patch pode depender da ordem exata e falhar em concorrência. Para coleções de domínio, endpoints específicos por ID costumam ser mais seguros que editar arrays posicionais.
Limites
- tamanho máximo do corpo;
- número máximo de operações;
- profundidade máxima;
- comprimento de path;
- tamanho de strings e arrays resultantes;
- tempo de processamento.
Resposta
A API pode retornar o recurso atualizado ou 204 No Content. Inclua novo ETag.
res.setHeader('ETag', `"user-${updated.version}"`);
res.status(200).json(toUserResponse(updated));OpenAPI
Documente application/json-patch+json, operações aceitas, paths permitidos e erros.
Consulte OpenAPI no Node.js.
Auditoria
Registre quem alterou, recurso, versão anterior, paths modificados e resultado. Não grave valores sensíveis completos.
Testes
- add, remove, replace e test;
- operação desconhecida;
- path ausente;
- path proibido;
- prototype pollution;
- remoção de campo obrigatório;
- array fora do limite;
- patch parcial que falha no meio;
- ETag desatualizado;
- autorização por campo;
- corpo excessivo.
Erros comuns
- aceitar qualquer path;
- não validar resultado final;
- aplicar diretamente no objeto original;
- ignorar prototype pollution;
- não tratar concorrência;
- permitir move e copy sem necessidade;
- usar arrays posicionais para recursos complexos;
- não limitar operações;
- confundir JSON Patch com Merge Patch.
JSON Patch versus Merge Patch
JSON Merge Patch usa um documento semelhante ao recurso e é mais simples para substituir ou remover propriedades. JSON Patch oferece operações explícitas, arrays e test. Escolha um formato e documente claramente.
Checklist
- Content-Type validado;
- operações em allowlist;
- paths autorizados;
- segmentos perigosos bloqueados;
- limites configurados;
- aplicação atômica;
- schema final validado;
- ETag ou versão;
- auditoria;
- testes adversariais.
Conclusão
JSON Patch no Node.js oferece atualizações parciais expressivas e padronizadas. Ele reduz payloads e permite pré-condições com test, mas exige controle rigoroso dos paths e do resultado.
Use allowlists, autorização por campo, validação completa e optimistic locking. Com limites e auditoria, PATCH se torna uma operação segura em vez de uma porta para modificar qualquer parte do documento.
Consulte o RFC 6902 sobre JSON Patch e o RFC 6901 sobre JSON Pointer.



