HTTP-методы и динамические эндпоинты
Суффикс в имени файла задаёт HTTP-метод, скобки — динамический параметр: products.post.ts ловит POST, [id].get.ts — GET по идентификатору.
Суть: имя файла обработчика можно суффиксовать .get, .post, .put, .delete — он сработает только на этот метод. Динамические сегменты [id] читаются через getRouterParam, тело POST/PUT — через readBody. Так строится полноценный REST-эндпоинт.
Реальный API различает методы: GET читает, POST создаёт, PUT обновляет, DELETE удаляет. Nuxt кодирует метод прямо в имени файла через суффикс. server/api/products.get.ts сработает только на GET, server/api/products.post.ts — только на POST. Один и тот же путь /api/products обслуживается разными файлами в зависимости от метода.
// server/api/products.get.ts — список
export default defineEventHandler(() => {
return getAllProducts()
})
// server/api/products.post.ts — создание
export default defineEventHandler(async (event) => {
const body = await readBody(event) // тело запроса
return createProduct(body)
})
Динамические эндпоинты работают как в страницах — через квадратные скобки. server/api/products/[id].get.ts матчит /api/products/42, а параметр читается хелпером getRouterParam:
// server/api/products/[id].get.ts
export default defineEventHandler((event) => {
const id = getRouterParam(event, "id") // "42"
const product = findProduct(id)
if (!product) {
throw createError({ statusCode: 404, statusMessage: "Нет товара" })
}
return product
})
REST на файлах server/api GET /api/products -> products.get.ts POST /api/products -> products.post.ts GET /api/products/42 -> products/[id].get.ts DELETE /api/products/42 -> products/[id].delete.ts тело -> readBody(event) параметр -> getRouterParam(event, "id")
Серверные маршруты Nuxt снимают целый класс задач, ради которых раньше поднимали отдельный бэкенд. Прокси к стороннему API, чтобы спрятать ключ; вебхуки от платёжных систем; генерация PDF или OG-картинок; работа с базой данных; отправка писем — всё это живёт в server/ рядом с фронтендом, в одном репозитории и одном деплое. Для небольших и средних проектов это огромное упрощение: не нужно поднимать, версионировать и стыковать два отдельных сервиса. Nuxt становится единой кодовой базой, где фронтенд и бэкенд говорят на одном языке и собираются вместе.
Как работает под капотом
Nitro строит таблицу маршрутов, где ключ — комбинация метода и пути. При запросе он находит обработчик по методу и шаблону, извлекает параметры из URL и складывает их в event.context.params (откуда их и берёт getRouterParam). Хелперы h3 — readBody, getQuery, getRouterParam — это удобные обёртки над разбором запроса. На неподходящий метод вернётся 405, на несуществующий путь — 404.
Смоделируем диспетчеризацию по методу и пути с извлечением параметра:
// Роутер по (метод + путь) с динамическим [id].
const routes = [
{ method: "GET", pattern: "/api/products", fn: () => ["A", "B"] },
{ method: "POST", pattern: "/api/products", fn: (e) => "создан: " + e.body.name },
{ method: "GET", pattern: "/api/products/:id", fn: (e) => "товар " + e.params.id },
{ method: "DELETE", pattern: "/api/products/:id", fn: (e) => "удалён " + e.params.id },
];
function dispatch(method, path, event = {}) {
for (const r of routes) {
if (r.method !== method) continue;
const rp = r.pattern.split("/"), up = path.split("/");
if (rp.length !== up.length) continue;
const params = {};
let ok = true;
for (let i = 0; i < rp.length; i++) {
if (rp[i].startsWith(":")) params[rp[i].slice(1)] = up[i];
else if (rp[i] !== up[i]) { ok = false; break; }
}
if (ok) return r.fn({ ...event, params });
}
return "404";
}
console.log(dispatch("GET", "/api/products"));
console.log(dispatch("POST", "/api/products", { body: { name: "Книга" } }));
console.log(dispatch("GET", "/api/products/42"));
console.log(dispatch("DELETE", "/api/products/7"));
Попробуй сам ▶ — обработчик выбирается по паре «метод + путь», а :id вытаскивается в параметры. Это упрощённая логика серверного роутера Nitro.
Хороший дизайн API — половина успеха фулстек-приложения. Придерживайтесь предсказуемых соглашений: существительные во множественном числе для коллекций (/api/products), идентификатор в пути для одной сущности (/api/products/42), а метод выражает намерение. GET ничего не меняет и безопасен для повтора; POST создаёт; PUT или PATCH обновляют; DELETE удаляет. Такая дисциплина делает API самодокументируемым: по одному взгляду на путь и метод понятно, что произойдёт.
Частые ошибки
- Один файл на все методы. Лучше разнести
.get/.postпо файлам — чище и явнее. - Читать тело синхронно.
readBodyасинхронный — нуженawait. - Не валидировать вход. Тело и параметры приходят от клиента — проверяйте их перед использованием.
Best practices
- Разделяйте обработчики по методам через суффиксы — это самодокументируемый REST.
- Параметры — через
getRouterParam, тело — черезawait readBody, query — черезgetQuery. - Несуществующие сущности —
createError({ statusCode: 404 }); неверный вход — 400.
Итог: суффиксы методов и динамические сегменты превращают server/api в полноценный REST. Хелперы h3 достают тело и параметры. Дальше — серверное middleware и безопасность эндпоинтов.