Wewnątrz DenoX: routowanie plików, kawałki MVC oraz kontrakt AGENTS.md w Deno
W jaki sposób framework DenoX łączy Hono, routowanie oparte na plikach, kawałki funkcji, middleware do bezpieczeństwa globalnego oraz model pracy AGENTS.md oparty na specyfikacji dla agentów do programowania AI.
Konfiguracja serwera rzadko stanowi najważniejszą część projektu backendowego – ważne jest wdrażanie nowych funkcji. Rails, Laravel i Next.js przyciągnęły programistów, podejmując za nich decyzje dotyczące podstawowych elementów architektury, a Deno, dzięki swoim domyślnie bezpiecznym uprawnieniom, wbudowanemu TypeScriptowi oraz narzędziom w zestawie, jest naturalnym kandydatem do takiego samego podejścia. DenoX to framework full-stack open-source dla Deno, oparty na Hono, który ma na celu stworzenie dokładnie takiej zdecydowanej warstwy. Jego rozwiązania często pojawiających się pytań strukturalnych oraz sposób, w jaki zapisuje je dla agentów kodujących opartych na AI, to wzorce, które można wykorzystać w dowolnym backendzie opartym na TypeScriptie.
Luka, którą wypełnia zdecydowana warstwa
Deno 2 wprowadziło kompatybilność z npm, rejestr JSR, dojrzałą bibliotekę standardową oraz jeden plik binarny, który może sprawdzać kod, formatować go, testować, kompilować i pakować (patrz jak Deno 2.x rozwiązał problemy kompatybilności z Node i zmęczenia narzędziami dla dodatkowych informacji). To, czego nie może zapewnić prosty silnik wykonawczy, to jednolite rozwiązania w kwestiach, o których spierają się wszystkie zespoły: gdzie powinna znajdować się logika biznesowa, jak raportowane są błędy, kto weryfikuje konfigurację i gdzie znajduje się ograniczenie szybkości.
DenoX odpowiada na te pytania za pomocą jednej zasady: konwencja zamiast konfiguracji, weryfikowana przez narzędzia. Dokumentowane konwencje mogą ulegać zmianom; te sprawdzane w procesie CI pozostają niezmienne.
Ładowanie stron na podstawie plików przy użyciu generowanej i zapisanej tabeli
Trasy pochodzą z systemu plików. Dodanie pliku do katalogu pages dodaje nową adresację URL, a segmenty w nawiasach stają się parametrami:
src/frontend/pages/
├── index.ts → /
├── about/main.ts → /about
├── users/main.ts → /users
└── posts/[id].ts → /posts/:id
Odkrywanie nie odbywa się w czasie wykonywania. Uruchomienie polecenia deno task routes przeszukuje drzewo i generuje statyczną, deterministyczną tabelę tras. Dwa elementy sprawiają, że jest to rozwiązanie niezawodne:
- Trasy statyczne są zawsze rejestrowane przed trasami dynamicznymi, więc
/users/newnigdy nie może zostać wchłonięty przez/users/:id. W routerach typu first-match, takich jak Hono, kolejność rejestracji wpływa na zachowanie, a jej automatyczne generowanie eliminuje klasyczne źródło subtelnych błędów. - Generowany plik jest zapisywany w repozytorium, a proces CI zawodzi, jeśli jest przestarzały.
Strony to zwykłe funkcje
Strona to zwykły moduł TypeScript. Importuje typ Context z Hono oraz narzędzie do unikania błędów w tekście HTML:
import type { Context } from "hono";
import { escapeHtml } from "@/shared/html.ts";
Następnie eksportuje obiekt config, który wybiera układ oraz funkcję domyślną zwracającą łańcuch HTML:
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>`;
}
Warto zwrócić uwagę na funkcję escapeHtml. Parametr zapytania name jest kontrolowany przez użytkownika, a bezpośrednie wstawianie go do kodu HTML stanowi klasyczny przykład luki typu reflected XSS. W DenoX unikanie wykorzystania niepewnych danych wejściowych nie jest zaleceniem, lecz regułą zawartą w umowie inżynieryjnej projektu. Ponieważ silnik szablonów nie przeprowadza automatycznego unikania specjalnych znaków w zwracanych surowych ciągach tekstowych, każde wstawianie danych z zewnątrz musi przejść przez tę funkcję pomocniczą.
Kawałki funkcjonalności o stałej strukturze
Z perspektywy API każda funkcjonalność stanowi samodzielny kawałek o tej samej strukturze plików, przy czym każdy z tych plików pełni jedną konkretne funkcję:
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)
Moduł DTO przekształca dane wejściowe typu unknown w obiekt o określonym typie na granicy, dzięki czemu żadna część aplikacji nie musi obsługiwać surowych treści zapytań. Usługi zawierają wyłącznie reguły biznesowe i nie wiedzą nic o protokole HTTP ani HTML. Kontrolery to proste adaptery HTTP. Plik z trasami stanowi punkt początkowy kompozycji, w którym zależności są łączone za pomocą iniekcji konstruktora.
Usługi polegają na interfejsach repozytoriów, a nie na konkretnych klasach. Dlatego zastąpienie pamięci operacyjnej bazą danych typu Postgres lub Deno KV oznacza konieczność zmiany jednego pliku na każdą funkcjonalność.
Błędy jako wyjątki o określonym typie
Reguły biznesowe sygnalizują niepowodzenie poprzez rzucanie wyjątków o określonym typie. Poniższa metoda usługi odmawia utworzenia drugiego użytkownika o już istniejącej adrese e-mail:
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);
}
Sługa nie wybiera kodu stanu ani nie formatuje odpowiedzi. Jedyny, centralny obsługa błędów mapuje każdy typ wyjątku na spójną strukturę JSON i zapewnia, że ślady stosu nigdy nie trafiają do klientów. Należy pamiętać, że odsyłanie e-maila umożliwia wyliczanie kont, czemu warto unikać w punktach dostępu publicznych.
Bезpieczeństwo wdrożone raz, stosowane wszędzie
Ochronne mechanizmy wspólne dla wszystkich funkcji znajdują się w globalnym middleware, a nie w poszczególnych modułach: polityka bezpieczeństwa treści, wzmocnione nagłówki odpowiedzi, zasady CORS, kontrole CSRF oparte na źródle, ograniczenia szybkości działania według IP klienta, limity wielkości ciała wiadomości, czasy wygaśnięcia oraz maskowanie błędów wewnętrznych. Funkcje korzystają z tych mechanizmów, zamiast je ponownie implementować.
Konfiguracja jest traktowana w ten sam sposób. Każda zmienna środowiskowa jest analizowana, weryfikowana i zamykana w momencie uruchomienia procesu, a aplikacja odmawia uruchomienia, jeśli czegoś brakuje lub dane są błędne. W środowisku produkcyjnym CORS_ORIGIN=* jest bezwzględnie odrzucany. Szybkie wykrycie błędu przy starcie jest lepsze niż debugowanie częściowo skonfigurowanego serwisu w produkcji.
Trzy warstwy testów za jednym poleceniem
Ustawienia testowe wykraczają poza zwykłe stwierdzenia typu placeholder:
- Testy jednostkowe obejmują czystą logikę przy użyciu mocków rejestrujących wywołania i w ogóle nie wymagają uprawnień Deno.
- Testy integracyjne testują w pełni połączoną aplikację za pomocą
app.request(), sprawdzając kody stanu, treść odpowiedzi oraz nawet nagłówki bezpieczeństwa, bez konieczności otwierania socketu.
Deno.serve na tymczasowym porcie i wysyłają do niego rzeczywiste żądania fetch, w tym takie, które celowo aktywują ograniczenie szybkości, aby potwierdzić, że zwraca ono kod 429.Pełna procedura kontroli jakości, obejmująca formatowanie, sprawdzanie błędów składniowych, weryfikację starych tabel tras, ścisłą kontrolę typów oraz wszystkie warstwy testowe, jest uruchamiana za pomocą deno task ci, a pipeline GitHub Actions wykonywa dokładnie tę sekwencję.
Jeden polecenie deploju, brak obsługi danych uwierzytelniających
Repozytorium zawiera pliki konfiguracyjne dla Fly.io, Railway, Render, Docker oraz wzmocnioną jednostkę systemd dla VPS, a także pełną obsługę Deno Deploy. Jedno zadanie określa cele, wyświetla symulację lub wykonuje deploj:
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)
Narzędzie do wdrażania celowo nie obsługuje żadnych danych uwierzytelniających. Sprawdza warunki wstępne, pokazuje plan wraz z przypomnieniami dotyczącymi wymaganych zmiennych środowiskowych i pozostawia autoryzację oficjalnej linii poleceń każdej platformy. Sekrety w ogóle nie pojawiają się w ramach tego frameworka.
AGENTS.md jako obowiązująca umowa inżynierska
Najbardziej charakterystyczną cechą DenoX jest plik AGENTS.md znajdujący się w korzeniu repozytorium, który stanowi autorytatywną umowę zarówno dla ludzkich współpracowników, jak i agentów kodujących opartych na AI. Określa on zestaw technologii, definiuje kanoniczną strukturę katalogów oraz wymienia wspólne elementy, które nigdy nie powinny być ponownie tworzone: loggera, hierarchię wyjątków, strukturę odpowiedzi oraz moduł konfiguracji.
Zawiera on również opis procesu rozwoju opartego na specyfikacjach:
- Specyfikację taką jak
specs/feature.mdtworzy się z ustawieniemstatus: draft. - Człowiek ją sprawdza i zmienia status na
status: approved. - Dopiero po zatwierdzeniu praca przechodzi przez etapy architektury, planowania, implementacji, testów i dokumentacji.
Agenci otrzymują wyraźne polecenie, aby przestać pracę po napisaniu specyfikacji i czekać na jej zatwierdzenie przez człowieka, dzięki temu agent nie może sam zatwierdzić swojego planu, a następnie przepisać połowy kodu. Kompletny cykl referencyjny dla zarządzania użytkownikami pokazuje agentom ten wzorzec, a system CI mechanicznie egzekwuje ustalone zasady, nawet blokując budowę, gdy plik wygenerowany został ręcznie zmodyfikowany.
Gdy agenci piszą coraz więcej kodu, konwencje mają znaczenie tylko wtedy, gdy można je sprawdzać automatycznie; wersjonowanie kontraktu obok kodu oraz wspieranie tego procesu za pomocą CI przekształcają wytyczne w ramy kontrolne. Aby zapoznać się z powiązanym podejściem do plików instrukcji dla asystentów, zobacz umiejętność AGENTS.md Vercel dotyczącą najlepszych praktyk w React.
Ruch lokalny
Klonuj repozytorium, utwórz plik środowiskowy na podstawie przykładu i uruchom serwer rozwojowy:
git clone https://github.com/olavomello/denox.git
cd denox
cp .env.example .env
deno task dev
Następnie otwórz http://localhost:8000, wywołaj /api/users, celowo prześlij nieważne dane i sprawdź, czy obwój błędu pozostaje czysty i pozbawiony śladów stosu wywołań. Dostępna jest również wersja online. Projekt jest licencjonowany na zasadach MIT; jego plan rozwoju obejmuje adaptery Deno KV i Postgres, automatyczne rejestrację układu, dedykowany interfejs wiersza poleceń, moduł autoryzacji oraz generowanie dokumentacji OpenAPI – wszystko to ma być tworzone zgodnie z tym samym podejściem opartym najpierw na specyfikacji. Sprawdź stan repozytorium przed jego przyjęciem.
Główne wnioski
- Generuj tabele tras w czasie budowania, zarejestruj trasy statyczne przed dynamicznymi, zapisz wynik i pozwól systemowi CI odrzucić przestarzałe pliki.
- Dla każdej funkcji ustal stałą strukturę: DTO-y definiujące granice, repozytoria oparte na interfejsach, usługi niezależne od HTTP oraz proste kontrolery.
AGENTS.md, który wymaga ludzkiej aprobaty specyfikacji, i wdroż jego zasady w CI, aby obowiązywały zarówno agenty, jak i ludzi.