Головна / Статті / Усередині DenoX: маршрутизація файлів, шматки MVC та контракт AGENTS.md у Deno

Усередині DenoX: маршрутизація файлів, шматки MVC та контракт AGENTS.md у Deno

Як фреймворк DenoX поєднує Hono, маршрутизацію на основі файлів, функціональні шматки, глобальний середовище проміжного програмування для безпеки та робочий процес AGENTS.md, орієнтований на специфікацію, для агентів з кодуванням на основі ШІ.

1560 слів

Підключення сервера рідко є найважливішою частиною проекту з бекендом; справжньою цінністю є реалізація функцій. Rails, Laravel та Next.js приваблювали розробників, ухвалюючи за них вирішення щодо основних аспектів роботи, а Deno зі своїми за замовчуванням безпечними правами доступу, нативним TypeScript та вбудованими інструментами є природним кандидатом на подібне підходження. DenoX — це фреймворк повного стеку для Deno з відкритим кодом, створений на основі Hono, який намагається стати саме таким авторитетним шаром. Його відповіді на постійно повторювані структурні запитання та спосіб їх запису для AI-агентів, які займаються програмуванням, є шаблонами, які можна використовувати у будь-якому бекенді на TypeScript.

Прогалину, яку заповнює авторитетний шар

Deno 2 приніс сумісність з npm, реєстр JSR, зрілу бібліотеку стандартних функцій та єдиний бінарник, який може виконувати перевірку коду, форматування, тестування, компіляцію та пакетування (див. як Deno 2.x вирішив проблеми сумісності з Node та втоми від інструментів для додаткової інформації). Те, чого не може надати простий движок виконання, — це узгодженість щодо питань, про які постійно сперечаються команди: де має знаходитися бізнес-логіка, як повідомлятися про помилки, хто перевіряє конфігурацію та де реалізовується обмеження швидкості.

DenoX відповідає на ці питання за допомогою одного принципу: конвенції важливіші за налаштування, що підтверджується інструментами. Документовані конвенції можуть змінюватися; ті, що перевіряються в процесі CI, залишаються незмінними.

Маршрутизація на основі файлів із генерованою та збереженою таблицею

Маршрути походять з файлової системи. Додавання файлу у каталог pages додає відповідний URL, причому елементи у дужках стають параметрами:

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

Виявлення маршрутів не відбувається під час виконання. Запуск команди deno task routes проймає дерево маршрутів та генерує статичну, детерміновану таблицю маршрутів. Два фактори роблять цей підхід надійним:

  • Статичні маршрути завжди реєструються перед динамічними, тому /users/new ніколи не може бути поглинутим маршрутом /users/:id. У роутерах з правилом першого збігу, як-от Hono, порядок реєстрації впливає на поведінку, а його автоматичне генерування усуває класичну причину прихованих помилок.
  • Генерований файл зберігається у репозиторії, і процес CI завершується з помилкою, якщо він застарілий.

Сторінки — це звичайні функції

Сторінка — це звичайний модуль TypeScript. Він імпортує тип Context від Hono та інструмент для ескейпування HTML:

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

Потім він експортує об’єкт config, який вибирає формат лейауту та функцію за замовчуванням, що повертає рядок 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>`;
}

Варто звернути увагу на функцію escapeHtml. Параметр запиту name контролюється користувачем, і його пряме вставлення у маркап буде класичною формою XSS-уразливості. У DenoX ескейпування ненадійних даних — це не рекомендація, а правило, закріплене у технічних умовах проекту. Оскільки движок шаблонів не автоматично ескейпує дані та повертає їх у необробленому вигляді, кожне вставлення зовнішніх даних мусить проходити через цю допоміжну функцію.

Фрагменти функцій із фіксованою структурою

З боку API кожна функція є самодостатнім фрагментом із однаковим набором файлів, кожен з яких виконує одну задачу:

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)

Модуль DTO перетворює вхідні дані типу unknown на об’єкт із визначеним типом на межі, тож жоден елемент у стеку не працює з необробленими тілами запитів. Сервіси містять лише бізнес-правила та нічого не знають про HTTP чи HTML. Контролери є простими адаптерами HTTP. Файл маршрутів є центром композиції, де залежності підключаються через вставку конструктором.

Сервіси залежать від інтерфейсів репозиторіїв, а не від конкретних класів. Тому заміна оперативної пам’яті на базу даних Postgres чи Deno KV означає зміну одного файлу на кожну функціональність.

Помилки як типовані винятки

Бізнес-правила сигналізують про невдачу шляхом кидання типованих винятків. Метод сервісу нижче не дозволяє створювати другого користувача з уже існуючою електронною поштою:

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

Сервіс не вибирає код стану та не форматує відповідь. Один централізований обробник помилок перенаправляє кожен тип винятку у єдиний формат JSON та запобігає надсиланню слідів виконання коду до клієнтів. Зауважте, що відправка електронного листа назад дозволяє проводити енумерацію облікових записів, чого варто уникати на публічних кінцевих точках.

Безпека реалізована один раз та застосовується всюди

Захисні механізми, які стосуються кількох функцій, розташовані у глобальному мідлвейрі, а не в кожній окремій функції: політика безпеки контенту, посилені заголовки відповідей, правила CORS, перевірки CSRF на основі походження запиту, обмеження частоти запитів за IP-адресою клієнта, ліміти розміру тіла повідомлення, таймаути та маскування внутрішніх помилок. Функції використовують ці механізми, замість того щоб реалізовувати їх заново.

Конфігурація обробляється аналогічно. Кожна змінна середовища аналізується, перевіряється та фіксується під час запуску процесу, і додаток відмовляється запускатися, якщо щось бракує або має неправильну форму. У продакшені значення CORS_ORIGIN=* відхиляється безумовно. Швидке виявлення проблем під час запуску краще, ніж налагодження напівналаштованого сервісу в продакшені.

Три рівні тестування за одним командним запитом

Система тестування виходить за межі простої перевірки:

  • Тести модулів охоплюють чисту логіку за допомогою моків із запису викликів та зовсім не потребують дозволів Deno.
  • Інтеграційні тести перевіряють повністю налаштований додаток за допомогою app.request(), перевіряючи коди статусу, формат відповідей та навіть заголовки безпеки, не відкриваючи сокети.
  • Тести від початку до кінця запускають справжній Deno.serve на тимчасовому порту та здійснюють до нього запити за допомогою справжніх функцій fetch, включаючи той, який навмисно активує обмеження швидкості, щоб підтвердити отримання коду 429.
  • Повний контроль якості, який охоплює форматування, перевірку на помилки, перевірку таблиці маршрутів, сувору перевірку типів та всі рівні тестування, виконується за допомогою команди deno task ci, а пайплайн GitHub Actions виконує саме цю послідовність.

    Одна команда для розгортання, без обробки облікових даних

    У репозиторії є маніфести для Fly.io, Railway, Render, Docker та посилена одиниця systemd для VPS, а також першокласна підтримка Deno Deploy. Одна команда визначає цілі, друкує результати пробного виконання або здійснює розгортання:

    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)
    

    Інструмент розгортання навмисно ніколи не обробляє облікові дані. Він перевіряє передумови, показує план разом із нагадуваннями щодо необхідних змінних середовища та залишає автентифікацію на розсуд офіційного CLI кожної платформи. Конфіденційні дані зовсім не потрапляють у фреймворк.

    AGENTS.md як обов’язковий інженерний контракт

    Найхарактернішою частиною DenoX є файл AGENTS.md, розташований у корені репозиторію, який є авторитетним контрактом як для людських учасників, так і для AI-агентів-програмістів. У ньому вказується технологічний стек, визначається канонічна структура директорій та перелічуються спільні елементи, які ніколи не слід винаходити заново: логгер, ієрархія винятків, оболонка відповіді та модуль конфігурації.

    Він також описує робочий процес розробки, заснований на специфікаціях:

    1. Специфікацію, наприклад specs/feature.md, створюють із значенням status: draft.
    2. Людина переглядає її та змінює статус на status: approved.
    3. Лише після схвалення робота переходить до етапів архітектури, планування, реалізації, тестування та документування.

    Агентам чітко вказують припинити роботу як тільки специфікація написана та чекати схвалення від людини, тож агент не може схвалити власний план та потім переписати частину кодової бази. Повний цикл роботи з управлінням користувачами демонструє агентам цю схему, а системи CI механічно дотримуються цих правил, припиняючи процес будування, якщо створений файл було змінено вручну.

    Чим більше коду пишуть агенти, тим важливішими стають конвенції лише в тій мірі, в якій їх можна автоматично перевіряти; версіонування контракту поруч із кодом та підтримка його за допомогою CI перетворюють рекомендації на чіткі правила. Щоб дізнатися більше про файли інструкцій для асистентів, дивіться навичку AGENTS.md від Vercel щодо кращих практик React.

    Робота локально

    Клонуйте репозиторій, створіть файл середовища за зразком та запустіть сервер розробки:

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

    Потім відкрийте http://localhost:8000, викличте /api/users, навмисно надішліть некоректні дані та перевірте, чи залишається обгортка помилки чистою та без слідів виконання коду. Також доступна жива версія. Проєкт ліцензований під MIT; його запланований розвиток включає адаптери Deno KV та Postgres, автоматичну реєстрацію макетів, спеціалізований CLI, модуль автентифікації та генерацію OpenAPI, причому кожен з цих елементів має проходити через однаковий процес розробки, заснований на специфікаціях. Перед використанням перевірте репозиторій на його поточний стан.

    Ключові висновки

    • Генеруйте таблиці маршрутів під час збірки, реєструйте статичні маршрути перед динамічними, зберігайте результати та дозвольте системі CI відхиляти застарілі файли.
    • Надайте кожній функції фіксовану структуру: DTO для меж, репозиторії на основі інтерфейсів, сервіси без використання HTTP та легкі контролери.
  • Викидайте типовані винятки та перекладайте їх у одному місці, щоб відповіді були послідовними та щоб стек-трейси ніколи не потрапляли назовні.
  • Реалізуйте заходи безпеки у глобальному мідлвейрі та перевіряйте конфігурацію під час запуску, відхиляючи небезпечні значення, такі як варіант шаблону для CORS-оригіналу, у продакшн-середовищі.
  • Напишіть файл AGENTS.md, який вимагає людського схвалення специфікацій, та застосовуйте його правила у CI, щоб вони поширювалися як на агентів, так і на людей.