Accueil / Articles / À l’intérieur de DenoX : routage des fichiers, tranches MVC et contrat AGENTS.md sur Deno

À l’intérieur de DenoX : routage des fichiers, tranches MVC et contrat AGENTS.md sur Deno

Comment le framework DenoX combine Hono, un routage basé sur les fichiers, des tranches de fonctionnalités, un middleware de sécurité globale ainsi qu’un flux de travail AGENTS.md axé sur les spécifications pour des agents de codage IA.

1560 mots

La configuration d’un serveur n’est que rarement l’aspect le plus important dans un projet backend ; c’est plutôt le déploiement des fonctionnalités qui l’est. Rails, Laravel et Next.js ont séduit les développeurs en prenant à leur place les décisions techniques fondamentales, et Deno, avec ses permissions par défaut sécurisées, son TypeScript natif et ses outils intégrés, est un candidat idéal pour bénéficier du même traitement. DenoX est un framework full-stack open source pour Deno, basé sur Hono, qui vise précisément à jouer ce rôle de couche directive. Ses réponses aux questions structurelles récurrentes, ainsi que la manière dont il les formalise pour les agents de codage basés sur l’IA, constituent des modèles que vous pouvez réutiliser dans n’importe quel backend en TypeScript.

La lacune comblée par une couche directive

Deno 2 a apporté la compatibilité avec npm, le registre JSR, une bibliothèque standard mature ainsi qu’un seul fichier binaire capable de vérifier la syntaxe, de formater le code, de tester, de compiler et de regrouper les fichiers (voir comment Deno 2.x a résolu les problèmes de compatibilité avec Node et la fatigue liée aux outils pour plus de détails). Ce que un environnement de exécution basique ne peut pas fournir, c’est une convergence sur les questions qui font l’objet de débats au sein de chaque équipe : où se situe la logique métier, comment les erreurs sont signalées, qui valide la configuration et où s’applique la limitation de fréquence.

DenoX y répond par un seul principe : la convention avant la configuration, vérifiée par les outils. Les conventions documentées évoluent ; celles contrôlées dans les tests d’intégration restent inchangées.

Routage basé sur des fichiers avec un tableau généré et stocké

Les routes proviennent du système de fichiers. Ajouter un fichier dans le répertoire pages crée une URL, les segments entre crochets devenant des paramètres :

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

La découverte des routes n’a pas lieu en temps de exécution. L’exécution de deno task routes parcourt l’arborescence et génère un tableau de routage statique et déterministe. Deux éléments rendent ce système fiable :

  • Les routes statiques sont toujours enregistrées avant les routes dynamiques, de sorte que /users/new ne peut jamais être absorbé par /users/:id. Avec des routeurs qui utilisent le premier match comme Hono, l’ordre d’enregistrement influence le comportement, et sa génération automatique élimine une source classique de bugs subtils.
  • Le fichier généré est commité dans le répertoire de version, et les tests CI échouent s’il est obsolète.

Les pages sont de simples fonctions

Une page correspond à un module TypeScript ordinaire. Il importe le type Context de Hono ainsi qu’un outil d’échappement HTML :

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

Il exporte ensuite un objet config qui sélectionne un layout et une fonction par défaut qui renvoie une chaîne 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>`;
}

Le détail à noter est escapeHtml. Le paramètre de requête name est contrôlé par l’utilisateur, et son insertion directe dans le markup constituerait une faille classique de type XSS reflété. Dans DenoX, l’échappement des entrées non fiables n’est pas une recommandation, mais plutôt une règle inscrite dans le contrat technique du projet. Comme les pages renvoient des chaînes brutes sans moteur de templates effectuant d’échappement automatique, toute interpolation de données externes doit passer par cet outil d’aide.

Fragments de fonctionnalités à structure fixe

D’un point de vue API, chaque fonctionnalité est un fragment autonome composé du même ensemble de fichiers, chacun ayant une seule tâche :

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)

Le module DTO transforme les entrées inconnues en objets typés à la frontière, de sorte que les parties plus profondes de l’architecture n’ont pas affaire à des corps de requête bruts. Les services ne contiennent que des règles métier et ignorent tout de HTTP ou d’HTML. Les contrôleurs sont des adaptateurs HTTP légers. Le fichier de routes constitue le point de composition où les dépendances sont connectées via l’injection par constructeur.

Les services dépendent d’interfaces de repository plutôt que de classes concrètes. Remplacer le stockage en mémoire par Postgres ou Deno KV signifie donc modifier un fichier par fonctionnalité.

Erreurs sous forme d’exceptions typées

Les règles métier signalent un échec en lançant des exceptions typées. La méthode de service ci-dessous refuse de créer un deuxième utilisateur avec une adresse e-mail déjà existante :

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);
}

Le service ne choisit pas de code d’état ni ne formate de réponse. Un gestionnaire d’erreurs centralisé mappe chaque type d’exception à un envoi JSON cohérent et s’assure que les traces d’exécution n’atteignent jamais les clients. Notez que l’écho de l’e-mail permet l’énumération des comptes, ce qu’il convient d’éviter sur les endpoints publics.

Sécurité mise en œuvre une fois, appliquée partout

Les protections transversales sont intégrées dans un middleware global plutôt que dans chaque fonctionnalité : une politique de sécurité des contenus, des en-têtes de réponse renforcés, des règles CORS, des vérifications CSRF basées sur l’origine, des limites de fréquence liées à l’IP du client, des plafonds de taille pour le corps des messages, des délais d’attente et un masquage des erreurs internes. Les fonctionnalités les utilisent sans avoir à les réimplémenter.

La configuration est traitée de la même manière. Chaque variable d’environnement est analysée, validée et figée au démarrage du processus ; l’application refuse de s’exécuter si quelque chose manque ou est mal formaté. En production, CORS_ORIGIN=* est catégoriquement rejeté. Échouer rapidement au démarrage vaut mieux que de devoir déboguer un service partiellement configuré en production.

Trois niveaux de tests derrière une seule commande

L’installation des tests va au-delà d’une simple assertion de remplacement :

  • Les tests unitaires couvrent la logique pure en utilisant des mocks de enregistrement d’appels et n’ont absolument pas besoin de permissions Deno.
  • Les tests d’intégration testent l’application entièrement connectée via app.request(), en vérifiant les codes d’état, les enveloppes de réponse et même les en-têtes de sécurité, sans ouvrir de socket.
  • Tests bout en bout : ils lancent un véritable Deno.serve sur un port éphémère et l’interrogent avec des appels fetch réels, y compris un qui provoque délibérément le limiteur de vitesse afin de confirmer qu’il renvoie 429.
  • Le contrôle de qualité complet, couvrant le formatage, la vérification syntaxique, la vérification du tableau des routes obsolètes, le contrôle strict de types ainsi que tous les niveaux de tests, s’exécute avec deno task ci, et le pipeline GitHub Actions met en œuvre précisément cette séquence.

    Une seule commande de déploiement, sans gestion de crédences

    Le répertoire contient des fichiers de configuration pour Fly.io, Railway, Render, Docker ainsi qu’une unité systemd renforcée pour un VPS, en plus d’un support de premier ordre pour Deno Deploy. Une seule tâche permet de lister les cibles, d’exécuter un essai sans déploiement réel ou de lancer effectivement le déploiement :

    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)
    

    Outil de déploiement qui ne traite délibérément jamais les identifiants. Il vérifie les prérequis, affiche le plan ainsi que des rappels concernant les variables d’environnement nécessaires, et laisse l’authentification à la CLI officielle de chaque plateforme. Les secrets restent complètement en dehors du framework.

    AGENTS.md en tant que contrat d’ingénierie contraignant

    La partie la plus distinctive de DenoX est un fichier AGENTS.md situé à la racine du répertoire, qui sert de contrat officiel tant pour les contributeurs humains que pour les agents de codage IA. Il définit la pile technologique, décrit l’arborescence de dossiers canonique et énumère les primitives partagées qui ne doivent en aucun cas être réinventées : le générateur de logs, la hiérarchie des exceptions, l’enveloppe de réponse et le module de configuration.

    Il encode également un flux de travail de développement piloté par des spécifications :

    1. specs/feature.md est rédigée avec status: draft.
    2. status: approved.

    Les agents reçoivent l’instruction explicite de s’arrêter une fois la spécification rédigée et d’attendre l’approbation d’une personne ; ainsi, un agent ne peut pas approuver son propre plan puis réécrire la moitié du codebase. Un cycle de référence complet pour la gestion des utilisateurs montre aux agents ce modèle, et les outils CI appliquent mécaniquement ces conventions, en empêchant même la compilation si un fichier généré a été modifié manuellement.

    À mesure que les agents écrivent davantage de code, les conventions n’ont d’importance que dans la mesure où elles peuvent être vérifiées automatiquement ; en versionnant le contrat à côté du code et en l’accompagnant d’un système CI, les directives se transforment en règles de sécurité. Pour une approche similaire concernant les fichiers d’instructions destinés aux assistants, consultez la compétence AGENTS.md de Vercel pour les meilleures pratiques React.

    Lancer localement

    Clonez le répertoire, créez un fichier d’environnement à partir de l’exemple et lancez le serveur de développement :

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

    Ouvrez ensuite http://localhost:8000, appelez /api/users, envoyez délibérément des données invalides et vérifiez que l’enveloppe d’erreur reste propre, sans traces d’exécution. Une version en ligne est également disponible. Le projet est sous licence MIT ; son plan de développement prévoit des adaptateurs Deno KV et Postgres, une inscription automatique des layouts, une CLI dédiée, un module d’authentification ainsi que la génération d’OpenAPI, tous conçus pour suivre le même processus basé sur des spécifications en premier. Consultez le répertoire pour connaître son état actuel avant de l’adopter.

    Points clés

    • Générer les tables de routes au moment de la compilation, enregistrer les routes statiques avant les dynamiques, commiter le résultat et laisser le CI rejeter les fichiers obsolètes.
    • Donner à chaque fonctionnalité une structure fixe : DTOs de bordure, repositories basés sur des interfaces, services sans HTTP et contrôleurs légers.
  • Lancer des exceptions formatées et les traduire en un seul endroit afin que les réponses soient cohérentes et qu’aucune trace d’stack ne s’échappe jamais.
  • Intégrer la sécurité dans le middleware global et valider la configuration au démarrage, en rejetant les valeurs dangereuses comme une origine CORS wildcard en environnement de production.
  • Rédiger un fichier AGENTS.md exigeant l’approbation humaine des spécifications, et appliquer ses règles dans le CI afin qu’elles s’appliquent aussi bien aux agents qu’aux humains.