TS
#astro#api#endpoints#rest#ssr#dynamic-routes

Astro: API Route (endpoint) с динамическим путём

Создание REST endpoint в Astro: APIRoute, динамический сегмент [slug], GET/POST и опциональная авторизация по заголовку.

Как использовать

  1. Создайте файл в src/pages/api/ с именем метода (GET, POST и т.д.) и при необходимости динамическим сегментом [slug].
  2. Для работы по запросу укажите output: server или hybrid в astro.config и export const prerender = false в файле endpoint.
  3. Проверьте запросом: curl http://localhost:4321/api/.../view или через браузер.

Когда в Astro-проекте нужен REST API (счётчики просмотров, webhook, админка по токену), эндпоинты делают в src/pages/ в виде файлов с экспортами GET, POST и т.д. Проблема возникает при переходе с чисто статического сайта: в режиме output: "static" такие маршруты вызываются только при сборке, а динамические пути без getStaticPaths не генерируются. Симптомы: после деплоя запрос к /api/blog/post-1/view отдаёт 404 или endpoint срабатывает только при build. Ниже — как сделать API Route с динамическим сегментом, несколькими методами и проверкой авторизации, плюс команда проверки через curl.

Решение

API Routes в Astro — это серверные endpoints в src/pages/. В режиме output: "static" эндпоинты вызываются при сборке; для работы по запросу нужен режим server или hybrid и для конкретного файла — export const prerender = false.

Динамический endpoint (Endpoints | Astro Docs):

// src/pages/api/blog/[slug]/view.ts
import type { APIRoute } from "astro";

export const prerender = false; // on-demand в static/hybrid

export const GET: APIRoute = async ({ params }) => {
  const { slug } = params;
  // запрос к БД: const views = await getViewsCount(slug);
  const views = 0;
  return new Response(
    JSON.stringify({ slug, views }),
    {
      status: 200,
      headers: { "Content-Type": "application/json" },
    }
  );
};

export const POST: APIRoute = async ({ params, request }) => {
  const { slug } = params;
  // await incrementViews(slug);
  return new Response(
    JSON.stringify({ success: true }),
    {
      status: 200,
      headers: { "Content-Type": "application/json" },
    }
  );
};

Несколько HTTP-методов и ALL (HTTP methods):

// src/pages/api/products/[id].ts
import type { APIRoute } from "astro";

export const GET: APIRoute = async ({ params }) => {
  return new Response(
    JSON.stringify({ id: params.id, name: "Product" }),
    { status: 200, headers: { "Content-Type": "application/json" } }
  );
};

export const PUT: APIRoute = async ({ params, request }) => {
  const body = await request.json();
  return new Response(
    JSON.stringify({ success: true }),
    { status: 200, headers: { "Content-Type": "application/json" } }
  );
};

export const DELETE: APIRoute = async ({ params }) => {
  return new Response(
    JSON.stringify({ success: true }),
    { status: 200, headers: { "Content-Type": "application/json" } }
  );
};

export const ALL: APIRoute = async ({ request }) => {
  return new Response(
    JSON.stringify({ error: `Method ${request.method} not supported` }),
    { status: 405 }
  );
};

Проверка авторизации в API Route:

// src/pages/api/admin/posts.ts
import type { APIRoute } from "astro";

const ADMIN_TOKEN = import.meta.env.ADMIN_TOKEN;

export const GET: APIRoute = async ({ request }) => {
  const token = request.headers.get("Authorization")?.replace("Bearer ", "");
  if (token !== ADMIN_TOKEN) {
    return new Response(
      JSON.stringify({ error: "Unauthorized" }),
      { status: 401, headers: { "Content-Type": "application/json" } }
    );
  }
  const posts: unknown[] = [];
  return new Response(JSON.stringify(posts), {
    status: 200,
    headers: { "Content-Type": "application/json" },
  });
};
  • Имя файла с расширением .ts или .js задаёт путь: api/blog/[slug]/view.ts/api/blog/:slug/view.
  • Экспортируйте функции с именами методов: GET, POST, PUT, DELETE, ALL.
  • Контекст handler: { params, request, redirect } и др. (API Reference).

Проверка

  1. Локальный запрос к динамическому endpoint (сервер должен быть в режиме server/hybrid):
pnpm dev
curl -s "http://localhost:4321/api/blog/hello-world/view"

Ожидаемый вывод — JSON, например {"slug":"hello-world","views":0}. Если 404 — проверьте, что в astro.config.mjs указано output: "server" или "hybrid" и в файле endpoint есть export const prerender = false.

  1. Диагностика метода и заголовков:
curl -s -o /dev/null -w "%{http_code}" "http://localhost:4321/api/blog/test/view"
curl -s -X POST "http://localhost:4321/api/blog/test/view"

Первый запрос — GET, ожидается 200. Второй — POST, должен вернуть JSON с success: true (или вашу логику).

  1. Проверка авторизации (для endpoint с ADMIN_TOKEN):
curl -s -w "\n%{http_code}" "http://localhost:4321/api/admin/posts"
# без заголовка — 401
curl -s -w "\n%{http_code}" -H "Authorization: Bearer ваш_токен" "http://localhost:4321/api/admin/posts"
# с верным токеном — 200 и тело с данными

Типичные ошибки

  • Endpoint отдаёт 404 в dev или после деплоя — в статическом режиме динамические пути без getStaticPaths не создаются. Включите output: "server" или "hybrid" в конфиге и добавьте в файл endpoint export const prerender = false.
  • params пустой или undefined — убедитесь, что папка названа именно [slug] (квадратные скобки), а не :slug или {slug}. Имена сегментов берутся из имён папок/файлов.
  • На продакшене 404 при обращении к /api/… — на хостинге должен быть выбран adapter для Node/Netlify/Vercel и сборка с output: "server" или "hybrid".

Где применять

  • Разработка: pnpm dev при output: "server" или "hybrid".
  • Продакшен: деплой с адаптером (Node, Netlify, Vercel). Endpoint-файлы с prerender: false обрабатываются на сервере при каждом запросе.

Связанные сниппеты: Astro Actions: форма с валидацией Zod, Адаптеры для деплоя (Node, Netlify, Vercel), Статический вывод по умолчанию.

⚠️ В статическом режиме для динамических путей без getStaticPaths endpoint не будет сгенерирован. Используйте output: "server" или "hybrid" и при необходимости export const prerender = false в файле endpoint.