REST API на Fresh

Fresh — это современный веб-фреймворк для Deno, ориентированный на создание быстрых и масштабируемых приложений с минимальной сложностью. Основной принцип Fresh — регенерация HTML на сервере без использования виртуального DOM, что делает его подход уникальным по сравнению с традиционными SPA-фреймворками. При разработке REST API это позволяет строить легковесные, быстрые и предсказуемые эндпоинты с минимальной нагрузкой на сервер.

REST API на Fresh обычно строится на основе роутинга через файлы. Каждое действие или ресурс сопоставляется с определённым файлом или директорией в проекте, что обеспечивает прямую связь между структурой файлов и URL маршрутов.


Организация роутинга

Роутинг в Fresh основан на файловой системе:

  • Директория routes/: каждая подпапка или файл внутри этой директории становится маршрутом.
  • HTTP-методы реализуются через экспорт функций GET, POST, PUT, DELETE и других.
  • Параметры маршрута задаются с помощью квадратных скобок: [id].ts будет соответствовать любому значению id.

Пример эндпоинта для получения ресурса по ID:

// routes/api/users/[id].ts
import type { Handlers } FROM "$fresh/server.ts";

export const handler: Handlers = {
  async GET(req, ctx) {
    const { id } = ctx.params;
    const user = await getUserById(id); // асинхронная функция получения данных
    if (!user) {
      return new Response(JSON.stringify({ error: "User not found" }), { status: 404 });
    }
    return new Response(JSON.stringify(user), { status: 200 });
  },
};

Здесь ключевые моменты:

  • ctx.params содержит значения динамических сегментов URL.
  • Асинхронность важна для работы с базами данных и внешними API.
  • Ответ формируется через Response, где можно задавать статус и тело ответа.

Работа с телом запроса

Для POST, PUT и PATCH-запросов часто необходимо получать тело запроса. В Fresh это делается через методы req.json() или req.text().

Пример:

export const handler: Handlers = {
  async POST(req) {
    try {
      const data = await req.json();
      const createdUser = await createUser(data);
      return new Response(JSON.stringify(createdUser), { status: 201 });
    } catch (error) {
      return new Response(JSON.stringify({ error: "Invalid data" }), { status: 400 });
    }
  },
};

Ключевые моменты:

  • req.json() автоматически парсит JSON-тело запроса.
  • Обработка ошибок обязательна для предотвращения падений сервера.
  • 201 Created используется для успешного создания ресурсов.

Работа с заголовками и статусами

Fresh позволяет полностью управлять HTTP-заголовками:

return new Response(JSON.stringify(data), {
  status: 200,
  headers: { "Content-Type": "application/json", "Cache-Control": "no-store" },
});

Особенности:

  • Content-Type должен соответствовать формату данных (JSON, текст, HTML).
  • Cache-Control позволяет контролировать кэширование на стороне клиента.
  • Можно добавлять кастомные заголовки, например, для авторизации или CORS.

Обработка ошибок и middleware-подход

Хотя Fresh не имеет встроенной системы middleware как Express, обработку ошибок и дополнительные действия можно реализовать через Handler composition:

function withAuth(handler: Handlers): Handlers {
  return {
    async GET(req, ctx) {
      const token = req.headers.get("Authorization");
      if (!token) return new Response("Unauthorized", { status: 401 });
      return handler.GET?.(req, ctx) || new Response(null, { status: 405 });
    }
  };
}

export const handler = withAuth({
  async GET(req, ctx) {
    return new Response("Protected data", { status: 200 });
  },
});

Такой подход позволяет:

  • Повторно использовать логику авторизации.
  • Разделять бизнес-логику и инфраструктурные функции.
  • Поддерживать чистоту кода при большом количестве эндпоинтов.

Работа с параметрами запроса и фильтрацией

Для REST API часто требуется поддержка query-параметров:

export const handler: Handlers = {
  async GET(req) {
    const url = new URL(req.url);
    const page = Number(url.searchParams.get("page") ?? 1);
    const LIMIT = Number(url.searchParams.get("limit") ?? 10);
    const users = await getUsers({ page, limit });
    return new Response(JSON.stringify(users), { status: 200 });
  },
};

Особенности:

  • Query-параметры извлекаются через URL и searchParams.
  • Поддержка пагинации и фильтров повышает масштабируемость API.
  • Обязательная проверка и конвертация типов для предотвращения ошибок.

Интеграция с базами данных

Fresh не навязывает конкретную базу данных. Наиболее распространённые подходы:

  • Deno DB или PostgreSQL через официальный драйвер Deno.
  • ORM типа Objection.js или Drizzle ORM для структурированного доступа.
  • Использование асинхронных функций с try/catch для управления ошибками.

Пример интеграции с PostgreSQL:

import { client } from "../. ./db.ts";

export const handler: Handlers = {
  async GET(req) {
    const users = await client.queryArray("SELECT id, name, email FROM users");
    return new Response(JSON.stringify(users.rows), { status: 200 });
  },
};

Ключевые моменты:

  • Использование асинхронного клиента для высокой производительности.
  • Явное указание SQL-запросов для простоты отладки.
  • Обработка ошибок и логирование — обязательная практика.

Практики безопасности

При создании REST API с Fresh рекомендуется:

  • CORS: настройка заголовков через Access-Control-Allow-Origin.
  • Валидация входных данных: предотвращает SQL-инъекции и XSS.
  • Авторизация: JWT или OAuth2 через проверку токенов в заголовках.
  • HTTPS: обязательный для защищённого обмена данными.

Производительность и кеширование

Fresh генерирует HTML на сервере, что уже уменьшает нагрузку на клиент. Для API это проявляется в:

  • Минимизации middleware слоёв.
  • Кешировании GET-запросов с помощью Cache-Control.
  • Использовании Deno Deploy для нативного edge-развёртывания.

Эти методы повышают скорость отклика и снижают задержки при масштабировании.


Особенности разработки и отладки

  • Локальный сервер запускается командой deno task start.
  • Горячая перезагрузка позволяет мгновенно видеть изменения.
  • TypeScript-поддержка встроена, что уменьшает количество ошибок в рантайме.
  • Логирование через стандартный console.log или сторонние библиотеки помогает отслеживать состояние API.

Fresh сочетает простоту роутинга, нативную поддержку TypeScript и лёгкую интеграцию с Deno, делая разработку REST API прозрачной, производительной и удобной для масштабирования сложных приложений.