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.

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
npm create hono@latest quest-api -- --template nodejs --install --pm npm
cd quest-api
npm run devDas 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.
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:
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:
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.