Startseite / Artikel / Im Inneren von DenoX: Dateirouting, MVC-Slices und ein AGENTS.md-Vertrag in Deno

Im Inneren von DenoX: Dateirouting, MVC-Slices und ein AGENTS.md-Vertrag in Deno

Wie das DenoX-Framework Hono, dateibasiertes Routing, Feature-Slices, globales Sicherheits-Middleware sowie einen auf Spezifikationen basierenden AGENTS.md-Arbeitsablauf für KI-Code-Agenten kombiniert.

1560 Wörter

Das Verbinden eines Servers ist selten der wertvolle Teil eines Backend-Projekts – vielmehr zählt das Bereitstellen neuer Funktionen. Rails, Laravel und Next.js gewannen Entwickler, indem sie die notwendigen Entscheidungen für sie trafen, und Deno mit seinen standardmäßig sicheren Berechtigungen, nativen TypeScript-Fähigkeiten sowie integrierten Werkzeugen ist ein idealer Kandidat für eine ähnliche Herangehensweise. DenoX ist ein Open-Source-Full-Stack-Framework für Deno, das auf Hono basiert und genau diese vorgefertigte Schicht darstellen soll. Seine Antworten auf wiederkehrende strukturelle Fragen sowie die Art und Weise, wie er sie für KI-Code-Agenten festhält, sind Muster, die in jedem TypeScript-Backend wiederverwendet werden können.

Die Lücke, die eine vorgefertigte Schicht füllt

Deno 2 brachte Kompatibilität zu npm, das JSR-Registry, eine ausgereifte Standardbibliothek sowie ein einziges Binärprogramm, das Linting, Formatierung, Tests, Kompilieren und Bündeln ermöglicht (siehe wie Deno 2.x die Node-Kompatibilitäts- und Tooling-Probleme gelöst hat für Hintergrundinformationen). Was ein reiner Laufzeitumgebung nicht bieten kann, ist Einigkeit zu den Fragen, über die jedes Team streitet: Wo die Geschäftslogik gehören soll, wie Fehler gemeldet werden, wer die Konfiguration überprüft und wo die Rate-Limiting-Regeln anwendbar sind.

DenoX beantwortet diese Fragen mit einem einzigen Prinzip: Konventionen statt Konfiguration, überprüft durch Tools. Dokumentierte Konventionen ändern sich; Konventionen, die in CI überprüft werden, bleiben konstant.

Dateibasiertes Routing mit einer generierten, gespeicherten Tabelle

Die Routen stammen aus dem Dateisystem. Das Hinzufügen einer Datei im pages-Verzeichnis ergibt eine URL, wobei die in Klammern gesetzten Abschnitte zu Parametern werden:

src/frontend/pages/
├── index.ts            →  /
├── about/main.ts       →  /about
├── users/main.ts       →  /users
└── posts/[id].ts       →  /posts/:id

Die Entdeckung der Routen erfolgt nicht zur Laufzeit. Das Ausführen von deno task routes durchläuft den Routenbaum und erzeugt eine statische, deterministische Routentabelle. Zwei Aspekte sorgen für Robustheit:

  • Statische Routen werden immer vor dynamischen registriert, sodass /users/new niemals von /users/:id überlagert werden kann. Bei Routen-Engines wie Hono, die nach dem Prinzip des ersten Treffers arbeiten, bestimmt die Registrierungsreihenfolge das Verhalten – ihre automatische Erstellung beseitigt eine klassische Quelle für subtile Fehler.
  • Die erzeugte Datei wird im Repository gespeichert, und der CI-Test fehlschlägt, wenn sie veraltet ist.

Seiten sind einfache Funktionen

Eine Seite ist ein gewöhnliches TypeScript-Modul. Es importiert den Context-Typ von Hono sowie einen Hilfsfunktionen zum Entschlüsseln von HTML:

import type { Context } from "hono";
import { escapeHtml } from "@/shared/html.ts";

Daraufhin exportiert es ein config-Objekt, das ein Layout sowie eine Standardfunktion auswählt, die einen HTML-String zurückgibt:

export const config = { layout: "default" } as const;export default function homePage(c: Context): string {
  const name = escapeHtml(c.req.query("name") ?? "world");
  return `<h1>Hello, ${name}!</h1>`;
}

Der bemerkenswerte Aspekt ist escapeHtml. Der Abfrageparameter name wird vom Benutzer gesteuert, und seine direkte Einbettung in die Markup-Struktur würde ein klassisches reflected XSS-Sicherheitsloch darstellen. In DenoX ist das Entschlüsseln unzuverlässiger Eingaben keine Empfehlung, sondern eine Vorschrift, die im ingenieurtechnischen Vertrag des Projekts festgelegt ist. Da die Seiten Rohstrings ohne automatisches Entschlüsseln durch einen Template-Engine zurückgeben, muss jede Einbettung externer Daten über diesen Hilfsfunktion erfolgen.

Funktionsbereiche mit fester Struktur

Auf der API-Seite ist jede Funktion ein eigenständiger Funktionsbereich mit derselben Menge an Dateien, wobei jede Datei eine einzige Aufgabe hat:

src/api/users/
├── user.model.ts        entities only
├── user.dto.ts          unknown → typed DTO (boundary validation)
├── user.repository.ts   interface + default implementation
├── user.service.ts      business rules only — no HTTP, no HTML
├── user.controller.ts   HTTP adapter only
└── user.routes.ts       composition root (constructor injection)

Das DTO-Modul wandelt Eingaben vom Typ unknown an der Grenze in typisierte Objekte um, sodass keine weiteren Komponenten im Stack mit rohen Anfragekörpern arbeiten müssen. Services enthalten ausschließlich Geschäftsregeln und kennen weder HTTP noch HTML. Controller sind schlanke HTTP-Adapter. Die Routes-Datei ist der Zusammenstellungspunkt, an dem Abhängigkeiten über Konstruktorinjektion verknüpft werden.

Services sind von Repository-Schnittstellen und nicht von konkreten Klassen abhängig. Der Ersatz des In-Memory-Speichers durch Postgres oder Deno KV bedeutet daher, dass pro Funktionalität nur eine Datei geändert werden muss.

Fehler als typisierte Ausnahmen

Geschäftsregeln signalisieren Fehler durch das Werfen typisierter Ausnahmen. Die untenstehende Service-Methode weigert sich, einen zweiten Benutzer mit einer bereits vorhandenen E-Mail-Adresse zu erstellen:

async create(dto: CreateUserDto): Promise<User> {
  const existing = await this.repository.findByEmail(dto.email);
  if (existing !== null) {
    throw new ConflictException(`Email "${dto.email}" is already registered`);
  }
  return await this.repository.create(dto);
}

Dienst wählt weder einen Statuscode aus noch formatiert er eine Antwort. Ein zentraler Fehlerhandler ordnet jeden Ausnahmetyp einer einheitlichen JSON-Struktur zu und sorgt dafür, dass Stacktraces niemals die Clients erreichen. Beachten Sie, dass das Zurücksenden der E-Mail eine Auflistung von Konten ermöglicht, was an öffentlichen Endpunkten vermieden werden sollte.

Sicherheit einmal implementiert, überall angewendet

Kreuzfunktionale Schutzmaßnahmen befinden sich im globalen Middleware-Modul und nicht in jeder Funktion: eine Content Security Policy, verschärfte Antwortheader, CORS-Regeln, CSRF-Prüfungen basierend auf der Herkunft, Bandbreitenbeschränkungen nach Klienten-IP, Obergrenzen für die Größe des Anhangs, Zeitlimits sowie Maskierung interner Fehler. Funktionen nutzen diese Schutzmaßnahmen, anstatt sie erneut zu implementieren.

Auch die Konfiguration wird auf dieselbe Weise behandelt. Jede Umgebungsvariable wird analysiert, überprüft und beim Start des Prozesses festgelegt; die Anwendung weigert sich, zu starten, wenn etwas fehlt oder fehlerhaft ist. In der Produktion wird CORS_ORIGIN=* vollständig abgelehnt. Schnelles Scheitern beim Start ist besser, als einen halb konfigurierten Dienst in der Produktion zu debuggen.

Drei Testebenen hinter einem Befehl

Die Testumgebung geht über eine einfache Platzhalterprüfung hinaus:

  • Unit-Tests überprüfen die reine Logik mithilfe von Mocks zur Aufzeichnung von Aufrufen und benötigen keinerlei Deno-Rechte.
  • Integrationstests testen die vollständig vernetzte Anwendung über app.request(), wobei Statuscodes, Antwortstrukturen und sogar Sicherheitsheader überprüft werden – ohne dass ein Socket geöffnet wird.
  • End-to-End-Tests starten einen echten Deno.serve auf einem temporären Port und rufen diesen mit echten fetch-Anfragen auf, wobei eine Anfrage absichtlich den Rate-Limiter auslöst, um zu überprüfen, ob 429 zurückgegeben wird.
  • Das umfassende Qualitätskontrollsystem, das Formatierung, Linting, die Überprüfung der veralteten Route-Tabelle, strenge Typüberprüfungen sowie alle Testschichten umfasst, wird mit deno task ci ausgeführt, und der GitHub Actions-Pipeline führt genau diese Abfolge aus.

    Ein einziger Deploy-Befehl, keine Verwaltung von Zugangsdaten

    Das Repository enthält Manifeste für Fly.io, Railway, Render, Docker sowie eine gesicherte systemd-Einheit für einen VPS, zusätzlich zur erstklassigen Unterstützung von Deno Deploy. Eine einzige Aufgabe listet die Ziele auf, führt einen Probelauf durch oder vollzieht den Deployment:

    deno task deploy            # list targets
    deno task deploy fly        # dry run: steps + env reminders
    deno task deploy fly --run  # execute (auth delegated to the platform CLI)
    

    Das Deployment-Tool behandelt Absichtlich niemals Anmeldeinformationen. Es überprüft die Voraussetzungen, zeigt den Plan zusammen mit Erinnerungen an erforderliche Umgebungsvariablen und überlässt die Authentifizierung der offiziellen CLI jeder Plattform. Geheime Informationen bleiben völlig außerhalb des Frameworks.

    AGENTS.md als durchsetzbarer Ingenieurvertrag

    Der auffälligste Aspekt von DenoX ist eine AGENTS.md-Datei im Wurzelverzeichnis des Repositoriums, die als verbindlicher Vertrag sowohl für menschliche Mitwirkende als auch für KI-Programmieragenten dient. Sie legt den Technologiestack fest, definiert den kanonischen Verzeichnisbaum und listet die gemeinsam genutzten Grundkomponenten auf, die niemals neu entwickelt werden dürfen: der Logger, die Ausnahmehierarchie, das Antwortformat sowie das Konfigurationsmodul.

    Zusätzlich wird darin ein auf Spezifikationen basierender Entwicklungsprozess festgelegt:

    1. Eine Spezifikation wie specs/feature.md wird mit status: draft erstellt.
    2. Ein Mensch prüft sie und ändert den Status auf status: approved.
    3. Nur nach der Freigabe geht die Arbeit weiter mit Architektur, Planung, Implementierung, Tests und Dokumentation.

    Den Agenten wird ausdrücklich mitgeteilt, dass sie aufhören sollen, sobald die Spezifikation geschrieben wurde, und auf eine Freigabe durch einen Menschen warten müssen; dadurch kann ein Agent seinen eigenen Plan nicht freigeben und anschließend die Hälfte des Codebases umschreiben. Ein vollständiger Referenzzyklus für die Benutzermanagement zeigt den Agenten dieses Muster, und CI durchsetzt die Konventionen mechanisch – indem der Build fehlschlägt, sobald eine generierte Datei von Hand geändert wurde.

    Da die Agenten immer mehr Code schreiben, sind Konventionen nur dann von Bedeutung, wenn sie automatisch überprüft werden können; das Versionieren des Kontrakts neben dem Code in Kombination mit CI verwandelt Leitlinien in Schutzmechanismen. Für eine ähnliche Sichtweise zu Anweisungsdateien für Assistenten siehe Vercels AGENTS.md-Fähigkeit für React-Best Practices.

    Lokale Ausführung

    Klonen Sie das Repository, erstellen Sie eine Umgebungsdatei anhand des Beispiels und starten Sie den Entwicklungsserver:

    git clone https://github.com/olavomello/denox.git
    cd denox
    cp .env.example .env
    deno task dev
    

    Öffnen Sie anschließend http://localhost:8000, rufen Sie /api/users auf, senden Sie absichtlich ungültige Daten und überprüfen Sie, ob die Fehlermeldung sauber bleibt und keine Stack-Traces enthält. Es steht auch eine Live-Version zur Verfügung. Das Projekt ist unter der MIT-Lizenz veröffentlicht; zu seinem geplanten Entwicklungsplan gehören Adapter für Deno KV und Postgres, automatische Registrierung von Layouts, eine eigene CLI, ein Authentifizierungsmodul sowie die Erstellung von OpenAPI-Dokumentationen – alles soll nach demselben prozessorientierten Vorgehen erstellt werden. Überprüfen Sie vor der Nutzung den aktuellen Zustand des Repositoriums.

    Haupterkenntnisse

    • Erstellen Sie Routentabellen zur Build-Zeit, registrieren Sie statische Routen vor dynamischen, speichern Sie die Ergebnisse ab und lassen Sie CI veraltete Dateien ablehnen.
    • Geben Sie jeder Funktion eine feste Struktur: Grenz-DTOs, auf Schnittstellen basierende Repositorien, services ohne HTTP-Aufrufe sowie schlanke Controller.
  • Werfen Sie typisierte Ausnahmen aus und übersetzen Sie diese an einem Ort, damit die Antworten konsistent sind und niemals Stack-Traces durchsickern.
  • Platzieren Sie die Sicherheit im globalen Middleware und validieren Sie die Konfiguration beim Start, wobei unsichere Werte wie Wildcard-CORS-Quellen in der Produktion abgelehnt werden.
  • Schreiben Sie ein AGENTS.md, das eine menschliche Freigabe der Spezifikationen erfordert, und setzen Sie dessen Regeln in CI durch, damit sie sowohl Agenten als auch Menschen binden.