TypeScript mit Hono RPC: typsichere APIs ohne Codegenerierung
AI generated
type
TypeScript · Backend · Edge
TypeScript mit Hono RPC
Typsichere APIs ohne Codegenerierung

Hono RPC erzeugt einen vollständig typisierten API-Client direkt aus dem Rückgabetyp der Serverrouten, ohne separaten Codegenerierungsschritt und ohne dupliziertes Schema. Der Server ist gleichzeitig die einzige Quelle der Wahrheit für Endpunkt, Payload und Antwort.

9 Min. Lesezeit Hono RPC Edge-Runtimes

1. Hono als schlankes, Runtime-agnostisches Web-Framework

Hono ist ein minimalistisches Web-Framework, das bewusst auf Web-Standard-APIs wie Request und Response aufbaut, statt eine eigene Node.js-spezifische Abstraktion zu erfinden. Dadurch läuft dieselbe Codebasis unverändert auf Cloudflare Workers, Deno, Bun, Node.js und weiteren Runtimes, ohne Adapter-Schicht in der Anwendungslogik.

Die Routendefinition folgt einem verketteten, Express-ähnlichen Stil, ist dabei aber von Grund auf mit TypeScript-Generics gebaut. Jede Methode wie get oder post gibt eine neue, erweiterte App-Instanz zurück, deren Typ alle bisher registrierten Routen kennt, was später die Grundlage für den typsicheren Client bildet.

Diese Bauweise unterscheidet Hono deutlich von größeren Frameworks, die häufig eine eigene Laufzeitumgebung mit fest verdrahteten Node.js-APIs voraussetzen. Weil Hono auf wenige Kernkonzepte setzt, bleibt die Lernkurve flach, während gleichzeitig genug Erweiterbarkeit über Middleware für reale Produktionsanforderungen erhalten bleibt. Gerade für kleinere Microservices oder API-Gateways zahlt sich diese Schlankheit direkt in kürzeren Kaltstartzeiten aus.


// src/index.ts
import { Hono } from 'hono';

const app = new Hono()
  .get('/posts/:id', (c) => {
    const id = c.req.param('id');
    return c.json({ id, title: 'Beispielbeitrag' });
  });

export default app;
export type AppType = typeof app;

2. AppType: der Vertrag zwischen Server und Client

Der entscheidende Baustein von Hono RPC ist die Zeile export type AppType = typeof app. Statt ein separates OpenAPI-Dokument oder eine GraphQL-Schemadatei zu pflegen, wird der TypeScript-Typ der App-Instanz selbst zum Vertrag. Jede Route, jeder Parameter und jeder Rückgabetyp aus c.json() ist darin bereits enthalten.

Damit dieser Typ im Client verfügbar ist, muss er lediglich importiert werden, üblicherweise über ein gemeinsames Package in einem Monorepo oder über einen relativen Import bei Full-Stack-Projekten mit gemeinsamem TypeScript-Setup. Der eigentliche JavaScript-Code der Serverimplementierung wird dabei nicht mitgeliefert, nur der Typ, sodass Serverlogik und Client-Bundle sauber getrennt bleiben.

3. Der hc-Client: Typinferenz statt Codegenerierung

Mit der Funktion hc(baseUrl) erzeugt Hono einen Client, dessen komplette Struktur, verschachtelte Pfadsegmente, HTTP-Methoden und Parameter, aus dem importierten AppType abgeleitet wird. Ein Aufruf wie client.posts[':id'].$get({ param: { id: '42' } }) wird vom Compiler exakt gegen die Serverroute geprüft, inklusive der erwarteten Parameterstruktur.

Der Rückgabetyp der Anfrage entspricht exakt dem, was die Serverroute über c.json() zurückgegeben hat. Ändert sich auf dem Server ein Feldname oder wird ein Feld entfernt, meldet der Client-Code sofort einen Kompilierfehler an jeder Stelle, die dieses Feld verwendet, ganz ohne dass ein Codegenerierungsschritt zwischengeschaltet werden muss.


// client.ts
import { hc } from 'hono/client';
import type { AppType } from './index';

const client = hc<AppType>('https://api.mironsoft.de');

async function loadPost(id: string) {
  const res = await client.posts[':id'].$get({ param: { id } });
  if (!res.ok) {
    throw new Error(`Fehler ${res.status}`);
  }
  const post = await res.json();
  return post; // { id: string; title: string }
}

4. Request-Validierung mit Zod-Middleware

Für Anfragen mit Body, Query-Parametern oder Headern kombiniert man Hono typischerweise mit @hono/zod-validator. Die Middleware validiert eingehende Daten zur Laufzeit gegen ein Zod-Schema und macht das validierte, typisierte Ergebnis über c.req.valid('json') im Handler verfügbar, statt auf rohe, unvalidierte Werte zuzugreifen.

Der entscheidende Effekt für Hono RPC ist, dass der Typ des validierten Werts automatisch in den Routentyp einfließt. Der generierte Client kennt dadurch nicht nur die URL-Struktur, sondern auch die exakte, vom Zod-Schema abgeleitete Form des erwarteten Request-Bodys, sodass ein fehlerhafter Aufruf bereits beim Schreiben des Client-Codes auffällt.


import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';

const CreatePostSchema = z.object({
  title: z.string().min(3),
  content: z.string(),
});

const app = new Hono().post(
  '/posts',
  zValidator('json', CreatePostSchema),
  async (c) => {
    const data = c.req.valid('json');
    const post = await db.post.create({ data });
    return c.json(post, 201);
  },
);

export type AppType = typeof app;

5. Routen verketten statt separat registrieren

Für korrekte Typinferenz ist entscheidend, dass Routen tatsächlich verkettet werden, statt sie mit separaten Aufrufen wie app.get(...); app.post(...); zu registrieren. Nur die verkettete Form akkumuliert alle Routentypen in derselben App-Instanz, weil jede Methode eine neu typisierte Instanz zurückgibt, die die vorherige erweitert.

Bei größeren Anwendungen werden Teilrouten über app.route('/posts', postsRouter) eingebunden, wobei der Typ des Unterrouters ebenfalls exportiert und in die Elternroute eingemischt werden muss, damit der finale AppType wirklich sämtliche Endpunkte kennt.


// routes/posts.ts
import { Hono } from 'hono';

export const postsRouter = new Hono()
  .get('/', async (c) => c.json(await db.post.findMany()))
  .post('/', async (c) => c.json({ id: 'new-id' }, 201));

// index.ts
import { Hono } from 'hono';
import { postsRouter } from './routes/posts';

const app = new Hono().route('/posts', postsRouter);
export type AppType = typeof app;

6. Hono RPC im Vergleich zu tRPC

tRPC verfolgt ein ähnliches Ziel, typsichere APIs ohne Codegenerierung, arbeitet dabei aber mit einer eigenen Prozedur-Abstraktion aus Queries und Mutations statt regulärer HTTP-Routen. Hono RPC bleibt näher an klassischem REST, jede Route ist ein normaler HTTP-Endpunkt mit Pfad und Methode, der auch ohne den generierten Client, etwa von externen Systemen oder über curl, ganz normal ansprechbar bleibt.

Dieser Unterschied macht Hono RPC besonders attraktiv, wenn dieselbe API sowohl von einem eigenen typsicheren Frontend als auch von Drittsystemen konsumiert werden soll, während sich tRPC eher für in sich geschlossene Full-Stack-Anwendungen eignet, bei denen niemals ein fremder Client gegen die API sprechen muss.

7. Deployment auf Cloudflare Workers ohne Anpassung

Weil Hono ausschließlich Web-Standard-APIs verwendet, lässt sich dieselbe App-Definition ohne Änderung als Cloudflare Worker deployen. Der Export erfolgt über ein Objekt mit einer fetch-Methode, was exakt der Worker-Signatur entspricht, sodass kein zusätzlicher Adapter zwischen Hono und der Workers-Runtime nötig ist.

Für typisierte Zugriffe auf Cloudflare-spezifische Bindings wie KV-Namespaces oder D1-Datenbanken unterstützt Hono einen generischen Env-Typparameter, der beim Erzeugen der App-Instanz angegeben wird. Handler erhalten dadurch typisierten Zugriff auf c.env.MY_KV, ohne auf any zurückgreifen zu müssen.


type Bindings = {
  MY_KV: KVNamespace;
  DB: D1Database;
};

const app = new Hono<{ Bindings: Bindings }>().get('/cached/:key', async (c) => {
  const value = await c.env.MY_KV.get(c.req.param('key'));
  return c.json({ value });
});

export default app;

8. Typisierte Fehlerantworten im Client behandeln

Der von hc() erzeugte Client wirft bei einer HTTP-Fehlerantwort standardmäßig keine Exception, sondern liefert ein Response-ähnliches Objekt mit einem ok-Flag zurück. Der Aufrufer muss dieses Flag explizit prüfen, bevor er auf die Antwortdaten zugreift, was TypeScript über eine Typ-Verengung im Erfolgsfall unterstützt.

Definiert eine Route über c.json(data, 404) mehrere mögliche Statuscodes mit unterschiedlichen Rückgabetypen, kennt der Client-Typ diese Varianten ebenfalls und erlaubt es, anhand des Statuscodes zwischen unterschiedlich geformten Antwortkörpern zu unterscheiden, statt nur einen einzigen, generischen Fehlertyp anzunehmen.


const res = await client.posts[':id'].$get({ param: { id } });

if (res.status === 404) {
  const notFound = await res.json(); // { error: string }
  console.warn(notFound.error);
  return null;
}

if (!res.ok) {
  throw new Error(`Unerwarteter Status ${res.status}`);
}

return res.json();

9. Grenzen: Monorepo-Kopplung und Typ-Performance

Hono RPC funktioniert am reibungslosesten, wenn Server und Client denselben TypeScript-Compiler-Kontext teilen, üblicherweise innerhalb eines Monorepos mit Tools wie Turborepo oder Nx. Für vollständig getrennte Repositories muss der AppType über ein veröffentlichtes Package verteilt werden, was zusätzliche Build- und Versionierungsschritte erfordert.

Bei sehr großen Anwendungen mit hunderten verketteten Routen kann die Typinferenz spürbar langsamer werden, da der Compiler bei jeder Nutzung des Clients den gesamten akkumulierten Routentyp neu auflösen muss. In solchen Fällen hilft eine Aufteilung in mehrere kleinere Sub-Router mit jeweils eigenem, unabhängig exportiertem Typ. In der Praxis lohnt sich diese Aufteilung ohnehin schon aus Gründen der Wartbarkeit, sodass die spätere Typ-Performance selten der alleinige Auslöser für eine Umstrukturierung ist.

Merkmal Hono RPC tRPC REST mit manuell gepflegten Typen
Codegenerierung Keine, reine Typinferenz Keine, reine Typinferenz Meist ja, etwa via OpenAPI-Generator
API-Stil Klassisches REST über HTTP Eigene Prozedur-Abstraktion Klassisches REST
Fremdzugriff ohne Client Uneingeschränkt möglich Erschwert, prozedurgebunden Uneingeschränkt möglich
Edge-Tauglichkeit Nativ, Web-Standard-APIs Abhängig vom Adapter Abhängig vom Framework
Kopplung Server/Client Gemeinsamer TypeScript-Kontext nötig Gemeinsamer TypeScript-Kontext nötig Entkoppelt über Schema-Datei

Mironsoft

TypeScript-Migration, Typsicherheit und Team-Onboarding

JavaScript-Codebasis ohne Typsicherheit, aber keine Zeit für eine Rundum-Migration?

Wir migrieren bestehende JavaScript-Projekte schrittweise zu TypeScript, richten strikte Compiler-Einstellungen sauber ein und bringen Teams mit Code-Reviews und Style-Guides auf denselben Typsicherheits-Stand.

Migrations-Fahrplan

Schrittweise JS-zu-TS-Migration ohne Big-Bang-Risiko planen und umsetzen.

Strict-Mode-Einführung

tsconfig.json, ESLint-Regeln und CI-Checks für dauerhafte Typsicherheit aufsetzen.

Team-Onboarding

Entwickler mit Workshops und Code-Reviews in TypeScript-Best-Practices einarbeiten.

10. Zusammenfassung

TypeScript mit Hono RPC

Vertrag

AppType leitet den Client-Typ direkt aus der Serverroute ab

Client-Erzeugung

hc() ohne separaten Codegenerierungsschritt

Validierung

zValidator mit Zod fließt automatisch in den Routentyp ein

Runtime

Web-Standard-APIs laufen nativ auf Cloudflare Workers und mehr

11. FAQ: TypeScript mit Hono RPC

1Was ist der Unterschied zwischen Hono RPC und klassischer Codegenerierung?
Klassische Codegenerierung liest ein separates Schema wie OpenAPI und erzeugt daraus Client-Code in einem eigenen Build-Schritt. Hono RPC verzichtet auf diesen Schritt vollständig und leitet den Client-Typ direkt per TypeScript-Typinferenz aus dem exportierten AppType der Serverapp ab.
2Was genau muss vom Server zum Client exportiert werden?
Ausschließlich der TypeScript-Typ export type AppType = typeof app. Die eigentliche Serverimplementierung, also der JavaScript-Code der Handler, wird nicht mitgeliefert, nur die Typinformation.
3Warum müssen Routen verkettet statt einzeln registriert werden?
Jede Methode wie get oder post gibt eine neu typisierte App-Instanz zurück, die alle bisherigen Routen kennt. Werden Routen mit separaten Anweisungen registriert, geht diese Typakkumulation verloren und der finale AppType enthält nicht alle Endpunkte.
4Wie funktioniert Request-Validierung mit Hono RPC zusammen?
Mit der Middleware @hono/zod-validator wird ein Zod-Schema an eine Route gebunden. Der validierte, typisierte Wert steht im Handler über c.req.valid() zur Verfügung und fließt automatisch in den Routentyp und damit in den generierten Client ein.
5Kann eine mit Hono RPC gebaute API auch ohne den generierten Client genutzt werden?
Ja, jede Route bleibt ein normaler HTTP-Endpunkt mit Pfad und Methode, der auch von externen Systemen, über curl oder von einem Frontend in einer anderen Sprache ganz regulär angesprochen werden kann.
6Worin unterscheidet sich Hono RPC von tRPC grundsätzlich?
tRPC nutzt eine eigene Prozedur-Abstraktion aus Queries und Mutations statt klassischer HTTP-Routen. Hono RPC bleibt näher an REST, jeder Endpunkt bleibt ein regulärer HTTP-Aufruf mit Pfad und Methode.
7Läuft Hono ohne Änderungen auf Cloudflare Workers?
Ja, da Hono ausschließlich Web-Standard-APIs wie Request und Response verwendet und über eine Objekt-Export mit fetch-Methode exakt der Worker-Signatur entspricht, ist kein zusätzlicher Adapter notwendig.
8Wie greift man typsicher auf Cloudflare-Bindings wie KV oder D1 zu?
Über den generischen Env-Typparameter beim Erzeugen der App-Instanz, etwa new Hono<{ Bindings: Bindings }>(). Handler erhalten dadurch typisierten Zugriff auf c.env, ohne auf any zurückgreifen zu müssen.
9Wirft der hc-Client bei einer Fehlerantwort automatisch eine Exception?
Nein, der Client liefert ein Response-ähnliches Objekt mit einem ok-Flag zurück. Der Aufrufer muss dieses Flag explizit prüfen, bevor er auf die Antwortdaten zugreift.
10Wann wird Hono RPC bei großen Anwendungen langsamer?
Bei sehr vielen verketteten Routen kann die Typinferenz spürbar langsamer werden, weil der Compiler bei jeder Nutzung des Clients den gesamten akkumulierten Routentyp neu auflösen muss. Eine Aufteilung in mehrere Sub-Router mit eigenem Typ hilft dagegen.