mitmario.dev

Ändern und löschen

Node.js Sandbox 3 Min Lesezeit 3 BeispieleLektion 4 von 8

Lesen und anlegen kannst du jetzt. Es fehlen die beiden unangenehmen Fälle: einen Eintrag ändern und einen loswerden. Beim Ändern gibt es zwei Methoden, und der Unterschied zwischen ihnen ist keine Wortklauberei, sondern entscheidet, ob Daten verschwinden.

PUT ersetzt, PATCH ändert

PUT ersetzt, PATCH ändert
import express from "express";

const app = express();
app.use(express.json());

app.get("/", (req, res) =>
  res.type("text/plain").send(
    `curl -X PUT   -H "Content-Type: application/json" -d '{"titel":"Nur der Titel"}' http://localhost:3000/buch\n` +
      `curl -X PATCH -H "Content-Type: application/json" -d '{"titel":"Nur der Titel"}' http://localhost:3000/buch\n`
  )
);

let buch = { id: 1, titel: "Node in der Praxis", jahr: 2024, verlag: "Selbstverlag" };

// PUT ersetzt den Eintrag durch das, was ankommt. Die Kennung bleibt.
app.put("/buch", (req, res) => {
  buch = { id: buch.id, ...req.body };
  res.json(buch);
});

// PATCH aendert nur die Felder, die mitgeschickt wurden.
app.patch("/buch", (req, res) => {
  Object.assign(buch, req.body);
  res.json(buch);
});

app.listen(3000);

Beide Befehle stehen im Reiter „Browser”. Schick erst den einen, dann den anderen, und vergleiche, was vom Buch übrig bleibt.

Sieh dir an, was nach dem PUT vom Eintrag übrig ist: Das Feld verlag ist weg, obwohl niemand es gelöscht hat.

Das ist keine Panne, sondern die Bedeutung von PUT: „Der Eintrag unter dieser Adresse sieht ab jetzt so aus.” Was nicht mitgeschickt wurde, gehört ab jetzt nicht mehr dazu. PATCH sagt etwas anderes, nämlich „ändere daran diese Felder”, und lässt den Rest in Ruhe.

Die häufigste Panne in der Praxis kommt aus dieser Verwechslung. Ein Formular zeigt drei von sieben Feldern, schickt die drei per PUT und löscht damit die anderen vier. Es sieht im Browser richtig aus, denn die angezeigten Felder stimmen ja.

Einmal oder zweimal geschickt

Einmal oder zweimal geschickt
import express from "express";

const app = express();
app.use(express.json());

const buecher = [{ id: 1, titel: "Node in der Praxis", jahr: 2024 }];
let naechsteId = 2;

app.get("/buecher", (req, res) => res.json({ anzahl: buecher.length }));

// Wiederholbar: das Ergebnis haengt nur davon ab, was ankommt.
app.put("/buecher/1", (req, res) => {
  buecher[0] = { id: 1, ...req.body };
  res.json(buecher[0]);
});

// Nicht wiederholbar: das Ergebnis haengt davon ab, wie oft es ankommt.
app.post("/buecher", (req, res) => {
  const buch = { id: naechsteId++, ...req.body };
  buecher.push(buch);
  res.status(201).json(buch);
});

app.listen(3000);

Rechts steht die Anzahl. Schick das PUT zweimal und lade neu, dann das POST zweimal und lade wieder:

curl -X PUT -H "Content-Type: application/json" -d '{"titel":"Geändert"}' http://localhost:3000/buecher/1

curl -X POST -H "Content-Type: application/json" -d '{"titel":"Neu"}' http://localhost:3000/buecher

Zweimal PUT mit demselben Inhalt lässt genau denselben Zustand zurück wie einmal. Zweimal POST nicht. Dafür gibt es ein Wort, das dir in jeder API-Dokumentation begegnen wird: idempotent. GET, PUT und DELETE sind es, POST ist es nicht.

Das ist keine Theorie. Wenn eine Verbindung abreißt, weiß der Aufrufer nicht, ob seine Anfrage angekommen ist. Bei einer idempotenten Methode kann er sie einfach noch einmal schicken, und schlimmstenfalls war sie umsonst. Bei POST muss er raten, und genau daraus entstehen doppelte Bestellungen.

Die richtigen Codes

Drei Fälle, drei Antworten, und alle drei sind Gewohnheit statt Vorschrift.

200 mit dem geänderten Eintrag nach PUT und PATCH. Der Aufrufer sieht damit sofort, was wirklich dabei herausgekommen ist, und muss nicht nachladen. Gerade nach einem PATCH ist das nützlich, weil dort ja Felder unangetastet geblieben sind.

204 ohne Inhalt nach DELETE. Es gibt nichts mehr zu zeigen, und 204 ist genau dafür da: hat geklappt, kein Körper. Ein res.status(204).json(...) wäre ein Widerspruch in sich.

404, wenn es die Kennung nicht gibt. Das gilt für alle drei Methoden gleichermaßen. Ändern kann man nur, was da ist.

Das zweite DELETE

Das zweite DELETE
import express from "express";

const app = express();

const buecher = [{ id: 1, titel: "Node in der Praxis" }];

app.get("/", (req, res) =>
  res.type("text/plain").send("curl -i -X DELETE http://localhost:3000/buecher/1\n")
);

app.delete("/buecher/:id", (req, res) => {
  const platz = buecher.findIndex((eintrag) => eintrag.id === Number(req.params.id));

  if (platz === -1) {
    return res.status(404).json({ fehler: "nicht gefunden" });
  }

  buecher.splice(platz, 1);

  // 204 heisst: hat geklappt, und ich habe dir nichts zu sagen. Ein
  // Koerper waere hier nicht erlaubt, res.end() beendet ohne Inhalt.
  res.status(204).end();
});

app.listen(3000);

Schick den Befehl von der Seite zweimal ab. Beim ersten Mal kommt ein 204, beim zweiten ein 404.

Hier scheiden sich die Geister, und du wirst beide Varianten im Netz finden. Das zweite DELETE auf denselben Eintrag: 404, weil es ihn nicht mehr gibt? Oder 204, weil der gewünschte Zustand ja eingetreten ist?

Dieser Kurs antwortet mit 404, aus einem einfachen Grund: DELETE /buecher/2 und GET /buecher/2 fragen nach demselben Ding, und es wäre seltsam, wenn die eine Anfrage „gibt es nicht” sagt und die andere „alles in Ordnung”. Ein Aufrufer, der einen Tippfehler in der Kennung hat, erfährt es so auch.

Das steht nicht im Widerspruch zur Idempotenz, auch wenn es sich erst einmal so anfühlt. Idempotent heißt: derselbe Zustand danach, nicht dieselbe Antwort. Und der Eintrag ist nach dem zweiten Aufruf genauso weg wie nach dem ersten.

Zum Mitnehmen

PUT ersetzt den ganzen Eintrag, PATCH ändert nur die genannten Felder. Ein PUT ohne ein Feld löscht dieses Feld, und das ist kein Versehen, sondern seine Bedeutung.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.