Маршруты API

fresh — это современный фреймворк для разработки приложений на базе Deno, ориентированный на максимальную производительность и реактивность. Одной из ключевых возможностей является работа с API-маршрутами, позволяющая создавать серверные эндпоинты для обработки запросов.

Определение маршрутов API

В fresh каждый маршрут API создаётся как отдельный файл в директории routes. Структура проекта может выглядеть следующим образом:

/routes
  ├─ api/
  │   ├─ users.ts
  │   └─ products.ts
  └─ index.tsx

Файлы внутри папки api автоматически интерпретируются как серверные обработчики, где имя файла соответствует пути маршрута. Например, routes/api/users.ts будет доступен по URL /api/users.

Структура файла маршрута

Маршрут API в fresh представляет собой экспорт функций, соответствующих HTTP-методам:

import { Handlers } from "$fresh/server.ts";

export const handler: Handlers = {
  async GET(req, ctx) {
    return new Response(JSON.stringify({ message: "GET запрос получен" }), {
      headers: { "Content-Type": "application/json" },
    });
  },

  async POST(req, ctx) {
    const data = await req.json();
    return new Response(JSON.stringify({ received: data }), {
      headers: { "Content-Type": "application/json" },
    });
  },
};
  • GET — обрабатывает запросы на получение данных.
  • POST — обрабатывает отправку данных на сервер.
  • req — объект запроса, предоставляет доступ к параметрам, телу и заголовкам.
  • ctx — контекст запроса, включающий параметры маршрута и другие данные.

Обработка параметров маршрута

Параметры маршрута задаются с помощью квадратных скобок в имени файла. Например, маршрут для пользователя по id:

routes/api/users/[id].ts

Доступ к параметру осуществляется через объект ctx.params:

export const handler: Handlers = {
  GET(_req, ctx) {
    const { id } = ctx.params;
    return new Response(JSON.stringify({ userId: id }), {
      headers: { "Content-Type": "application/json" },
    });
  },
};

Работа с query-параметрами

Query-параметры извлекаются через URL из объекта Request:

export const handler: Handlers = {
  GET(req) {
    const url = new URL(req.url);
    const page = url.searchParams.get("page") || "1";
    return new Response(JSON.stringify({ page }), {
      headers: { "Content-Type": "application/json" },
    });
  },
};

Асинхронная обработка и база данных

Маршруты API позволяют работать с асинхронными операциями, включая запросы к базе данных:

import { getUserById } from "../. ./db/users.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: "Пользователь не найден" }), { status: 404 });
    }
    return new Response(JSON.stringify(user), {
      headers: { "Content-Type": "application/json" },
    });
  },
};

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

  • Все операции могут быть асинхронными.
  • Статусы HTTP можно задавать явно через объект Response.
  • Ошибки обрабатываются с использованием стандартных кодов HTTP.

Middleware в API-маршрутах

В fresh можно реализовать промежуточные функции для обработки аутентификации или логирования:

export const handler: Handlers = {
  async GET(req, ctx) {
    if (!req.headers.get("Authorization")) {
      return new Response(JSON.stringify({ error: "Не авторизован" }), { status: 401 });
    }
    return new Response(JSON.stringify({ message: "Доступ разрешён" }), {
      headers: { "Content-Type": "application/json" },
    });
  },
};

Middleware не требует отдельного файла — достаточно встроить проверки в обработчик. Для сложных сценариев можно создавать отдельные функции для повторного использования.

Совместимость с REST и JSON

Все маршруты API в fresh ориентированы на работу с REST-подходом и JSON. Форматирование ответа обязательно должно содержать заголовок:

headers: { "Content-Type": "application/json" }

Это обеспечивает корректное взаимодействие с фронтенд-приложением или внешними сервисами.

Динамическая маршрутизация

С помощью файлов [param].ts и [...rest].ts можно реализовать динамическую маршрутизацию и вложенные пути:

  • [id].ts — захват одного сегмента пути.
  • [...path].ts — захват всех оставшихся сегментов, полезно для маршрутов типа /api/files/*.

Работа с CORS

Для внешних API важно правильно настроить CORS:

export const handler: Handlers = {
  GET(_req) {
    return new Response(JSON.stringify({ message: "CORS включён" }), {
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
      },
    });
  },
};

Это позволяет делать запросы к API с других доменов без ошибок браузера.

Тестирование маршрутов

API-маршруты в fresh можно тестировать с помощью:

  • Deno Test — встроенный фреймворк для юнит-тестов.
  • HTTP-клиентыfetch, Postman, curl.

Пример теста на GET-запрос:

import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import { handler } from "./users.ts";

Deno.test("GET /api/users returns status 200", async () => {
  const req = new Request("http://localhost/api/users");
  const resp = await handler.GET(req, {} as any);
  assertEquals(resp.status, 200);
});

Тесты помогают убедиться, что маршруты корректно обрабатывают запросы и возвращают ожидаемый формат данных.

Обработка ошибок

Встроенные методы HTTP позволяют возвращать корректные коды ошибок:

  • 400 Bad Request — некорректные данные.
  • 401 Unauthorized — отсутствует авторизация.
  • 404 Not Found — ресурс не найден.
  • 500 Internal Server Error — внутренняя ошибка сервера.

Пример обработки с try/catch:

export const handler: Handlers = {
  async GET(_req) {
    try {
      const data = await fetchData();
      return new Response(JSON.stringify(data), { headers: { "Content-Type": "application/json" } });
    } catch (err) {
      return new Response(JSON.stringify({ error: err.message }), { status: 500 });
    }
  },
};

Оптимизация производительности

  • Использование кеширования с Cache-Control.
  • Минимизация лишних вычислений внутри обработчика.
  • Прямое возвращение JSON без промежуточной сериализации больших объектов.

Маршруты API в fresh обеспечивают высокую производительность, гибкость и простоту интеграции с современными фронтенд-фреймворками.