Zum Inhalt springen
DasPeTe Tutorials

Eine kleine REST-API mit Hono bauen

Erstelle mit Hono eine Quest-Tracker-API in TypeScript: Routen, Statuscodes und Eingabeprüfung, alles in einer kleinen Datei.

Einsteiger20 Min.

Quest-Ziele

  • Ein Hono-Projekt auf Node.js aufsetzen
  • Routen zum Lesen, Anlegen, Ändern und Löschen schreiben
  • Mit den richtigen HTTP-Statuscodes antworten
  • Eingaben prüfen, statt ihnen zu vertrauen

Hono ist ein kleines, schnelles Web-Framework, das auf Webstandards aufbaut. Wenn du Express kennst, wird dir die API vertraut vorkommen, und es läuft gleichermaßen auf Node.js, Deno, Bun und Edge-Plattformen. In diesem Tutorial baust du eine API für einen Quest-Tracker, mit der du Quests anlegen, lesen, ändern und löschen kannst.

Das Projekt anlegen

bash
npm create hono@latest quest-api -- --template nodejs --install --pm npm
cd quest-api
npm run dev

Das Starterprojekt führt deinen Code mit Hot Reloading aus, der Server startet also bei jedem Speichern neu. Öffne src/index.ts und ersetze den Inhalt. Die Quests liegen in einer Map, damit sich dieses Tutorial auf die API konzentriert. Nach einem Neustart sind sie deshalb weg.

src/index.ts
import { serve } from '@hono/node-server';
import { Hono } from 'hono';

type Quest = { id: number; title: string; done: boolean };

const quests = new Map<number, Quest>();
let nextId = 1;

const app = new Hono();

app.get('/quests', (c) => c.json([...quests.values()]));

serve({ fetch: app.fetch, port: 3000 }, (info) => {
  console.log(`Listening on http://localhost:${info.port}`);
});

Jeder Route-Handler erhält einen Kontext, c, der die Anfrage und Hilfsmittel für die Antwort enthält. c.json() sendet JSON mit dem korrekten Content-Type.

Die übrigen Routen

Füge diese Routen über dem Aufruf von serve() ein:

src/index.ts
app.get('/quests/:id', (c) => {
  const quest = quests.get(Number(c.req.param('id')));
  if (!quest) return c.json({ error: 'Quest not found' }, 404);
  return c.json(quest);
});

app.post('/quests', async (c) => {
  const body = await c.req.json<{ title?: unknown }>().catch(() => null);
  const title = typeof body?.title === 'string' ? body.title.trim() : '';
  if (!title) return c.json({ error: 'A title is required' }, 400);

  const quest: Quest = { id: nextId++, title, done: false };
  quests.set(quest.id, quest);
  return c.json(quest, 201);
});

app.patch('/quests/:id', async (c) => {
  const quest = quests.get(Number(c.req.param('id')));
  if (!quest) return c.json({ error: 'Quest not found' }, 404);

  const body = await c.req.json<{ done?: unknown }>().catch(() => null);
  if (typeof body?.done !== 'boolean') {
    return c.json({ error: '"done" must be true or false' }, 400);
  }

  quest.done = body.done;
  return c.json(quest);
});

app.delete('/quests/:id', (c) => {
  const deleted = quests.delete(Number(c.req.param('id')));
  if (!deleted) return c.json({ error: 'Quest not found' }, 404);
  return c.body(null, 204);
});

Über Statuscodes kommuniziert eine API mit ihren Clients, es lohnt sich also, sie richtig zu wählen:

  • 201 Created sagt, dass jetzt eine neue Quest existiert.

  • 204 No Content sagt, dass das Löschen geklappt hat und es nichts zurückzuschicken gibt.

  • 400 Bad Request sagt, dass der Client etwas Ungültiges geschickt hat.

  • 404 Not Found sagt, dass es diese Quest nicht gibt.

Ausprobieren

Nimm curl in einem zweiten Terminal:

bash
curl -X POST localhost:3000/quests \
  -H 'content-type: application/json' \
  -d '{"title":"Slay the dragon"}'
# {"id":1,"title":"Slay the dragon","done":false}

curl -X PATCH localhost:3000/quests/1 \
  -H 'content-type: application/json' \
  -d '{"done":true}'
# {"id":1,"title":"Slay the dragon","done":true}

curl -i -X POST localhost:3000/quests \
  -H 'content-type: application/json' \
  -d '{}'
# HTTP/1.1 400 Bad Request
# {"error":"A title is required"}

Wie es weitergeht

Der nächste Schritt ist, die Map durch eine echte Datenbank zu ersetzen, damit deine Quests einen Neustart überstehen. SQLite ist ein guter Einstieg, weil es keinen eigenen Server braucht.

Kommentare

Die Kommentare sind GitHub Discussions: Zum Schreiben mit GitHub anmelden.

Die Kommentare kommen über giscus von GitHub. Beim Laden gehen deine IP-Adresse an giscus.app und GitHub.