mitmario.dev

Daten anlegen

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

Lesen war die eine Hälfte, jetzt kommt das Schreiben. Und mit dem Schreiben kommen Fragen, die beim Lesen niemand stellen musste: Auf welchen Pfad geht das? Was antwortet man darauf? Und wer entscheidet eigentlich, welche Nummer der neue Eintrag bekommt?

POST geht auf die Sammlung

Das ist die erste Regel und die einzige, bei der man sich verlaufen kann. Ein neuer Eintrag wird an /buecher geschickt, nicht an /buecher/5.

Der Grund ist schlicht: /buecher/5 gibt es noch nicht. Genau das ist ja der Zweck der Anfrage. Wer den Pfad zum Anlegen schon kennen müsste, hätte den Eintrag bereits.

Anlegen und die Antwort darauf
import express from "express";

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

// Ein POST gibt es in der Adresszeile nicht, deshalb steht der Befehl
// dafuer hier.
app.get("/", (req, res) =>
  res.type("text/plain").send(
    `curl -i -X POST -H "Content-Type: application/json" -d '{"titel":"HTTP verstehen"}' http://localhost:3000/buecher\n`
  )
);

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

app.post("/buecher", (req, res) => {
  const buch = { id: naechsteId++, titel: req.body.titel };
  buecher.push(buch);

  // 201 statt 200, die Adresse des neuen Eintrags als Kopfzeile, und der
  // Eintrag selbst im Koerper. Danach weiss der Aufrufer alles.
  res.status(201).location(`/buecher/${buch.id}`).json(buch);
});

app.listen(3000);

Ein POST lässt sich nicht in die Adresszeile tippen, deshalb steht der Befehl dafür im Reiter „Browser”. Mit -i siehst du auch die Kopfzeile Location:

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

Was zurückkommt

Drei Dinge gehören in die Antwort, und alle drei haben einen Zweck.

Der Statuscode 201 statt 200. Beide heißen „hat geklappt”, aber 201 heißt zusätzlich „und dabei ist etwas Neues entstanden”. Ein Aufrufer, der Buch führt, kann daran hängen, ob er seine Ansicht aktualisieren muss.

Der neue Eintrag im Körper. Er enthält jetzt Felder, die der Aufrufer nicht mitgeschickt hat, allen voran die Kennung. Ohne sie müsste er die Liste neu holen und raten, welcher der neue ist.

Die Kopfzeile Location. Sie sagt, unter welcher Adresse das Ding ab jetzt liegt. res.location() setzt sie, res.set("Location", ...) täte dasselbe. Für ein Programm ist das die bequemste Auskunft überhaupt: Es muss keinen Pfad zusammenbauen, sondern liest ihn ab.

Zweimal abgeschickt sind zwei Einträge

Zweimal abgeschickt sind zwei Einträge
import express from "express";

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

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

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

app.post("/buecher", (req, res) => {
  const buch = { id: naechsteId++, titel: req.body.titel };
  buecher.push(buch);
  res.status(201).location(`/buecher/${buch.id}`).json(buch);
});

app.listen(3000);

Im Reiter „Browser” steht der Bestand. Schick denselben Befehl zweimal ab und lade die Seite danach neu:

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

Zwei gleiche Anfragen, zwei Bücher. POST ist nicht wiederholbar, und das ist kein Fehler, sondern seine Bedeutung: Jeder Aufruf legt etwas an.

Das klingt harmlos und ist im Betrieb die häufigste Quelle für doppelte Datensätze. Eine wackelige Mobilverbindung, ein zweiter Klick auf den Absenden-Knopf, eine Wiederholungslogik im Client: Von deinem Server aus sind das drei identische Anfragen, und er kann nicht wissen, dass zwei davon Unfälle waren.

In Lektion 11.4 lernst du die Methoden kennen, bei denen das anders ist. Für heute reicht es, den Unterschied zu kennen und ihn nicht für einen Bug zu halten.

Wer die Kennung vergibt

Wer die Kennung vergibt
import express from "express";

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

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

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

app.get("/buecher/:id", (req, res) => {
  const buch = buecher.find((eintrag) => eintrag.id === Number(req.params.id));
  res.json(buch ?? { fehler: "nicht gefunden" });
});

// Der Aufrufer bestimmt die Kennung. Sieht bequem aus und ist es nicht.
app.post("/vom-aufrufer", (req, res) => {
  const buch = { id: req.body.id, titel: req.body.titel };
  buecher.push(buch);
  res.status(201).json(buch);
});

// Der Server bestimmt sie. Eine Zeile mehr, ein Problem weniger.
app.post("/vom-server", (req, res) => {
  const buch = { id: naechsteId++, titel: req.body.titel };
  buecher.push(buch);
  res.status(201).json(buch);
});

app.listen(3000);

Beide Wege liegen nebeneinander. Schick etwas an den einen, dann an den anderen, und schau danach im Reiter „Browser” nach, was im Bestand gelandet ist:

curl -X POST -H "Content-Type: application/json" -d '{"id":1,"titel":"Untergeschoben"}' http://localhost:3000/vom-aufrufer

curl -X POST -H "Content-Type: application/json" -d '{"titel":"Ordentlich angelegt"}' http://localhost:3000/vom-server

Die erste Route lässt den Aufrufer bestimmen, welche Nummer sein Eintrag bekommt. Er schickt die 1, die es schon gibt, und danach liegen zwei Bücher unter derselben Kennung. /buecher/1 findet das erste und liefert es aus. Der neue Eintrag ist da, aber über seine eigene Adresse nicht mehr erreichbar.

Die Kennung vergibt der Server, immer. Sie ist kein Wunsch des Aufrufers, sondern eine Auskunft an ihn, und deshalb steht sie in der Antwort und nicht in der Anfrage. Sobald mehrere Leute gleichzeitig anlegen, ist das der einzige Weg, bei dem am Ende keine zwei Einträge dieselbe Nummer tragen.

Der Zähler in den Beispielen ist die einfachste denkbare Umsetzung, und er hält genau so lange, wie der Prozess läuft. Ab Abschnitt 12 übernimmt die Datenbank diese Aufgabe, und zwar dauerhaft.

Zum Mitnehmen

POST geht auf die Sammlung, antwortet mit 201, schickt den neuen Eintrag mit und nennt in der Kopfzeile Location, wo er ab jetzt liegt.

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.