Як створити API роути в Nuxt?
Nuxt API роути - серверні ендпоінти в папці server/api/, які автоматично стають /api/* URL. Жодного окремого сервера, жодних налаштувань роутера. Шлях файлу і є маршрутом.
Теорія
TL;DR
- Файл
.tsуserver/api/Nuxt реєструє як ендпоінт автоматично - Кожен обробник загортається у
defineEventHandler(вимога H3, серверного рушія Nuxt) - Суфікс
.get.tsабо.post.tsу назві файлу обмежує роут одним HTTP-методом - Дані запиту читаєш через
getQuery(event),readBody(event),getRouterParam(event, 'name') - Помилки кидаєш через
createError({ statusCode: 404, message: '...' })
Швидкий приклад
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
if (!id) {
throw createError({ statusCode: 400, message: 'id не передано' })
}
return { id, name: 'Аліса' }
})
// GET /api/users/42 → { id: '42', name: 'Аліса' }
// POST /api/users/42 → 405 Method Not Allowed (автоматично)Суфікс .get.ts прив'язує файл до GET. Інший метод поверне 405 без жодного додаткового коду з твого боку.
Назви файлів і URL
Nuxt будує URL прямо зі шляху файлу:
server/api/users.ts→/api/usersserver/api/users/index.ts→/api/usersserver/api/users/[id].ts→/api/users/:id(всі методи)server/api/users/[id].get.ts→GET /api/users/:idserver/api/users/[id].delete.ts→DELETE /api/users/:id
Квадратні дужки [id] створюють динамічний сегмент. Кілька параметрів в одному шляху теж підтримуються: [userId]/posts/[postId].ts.
Читання даних запиту
Три хелпери покривають більшість ситуацій:
// Query string: GET /api/search?q=nuxt&limit=10
const query = getQuery(event) // { q: 'nuxt', limit: '10' }
// Тіло запиту: POST /api/users
const body = await readBody(event) // { name: 'Боб', email: '...' }
// Параметр маршруту: GET /api/users/42
const id = getRouterParam(event, 'id') // '42'readBody асинхронний. Забути await означає отримати Promise замість даних. Це найчастіший баг у нових Nuxt-проектах.
Де виконуються серверні роути
Серверні роути запускаються у Nitro, рушії Nuxt побудованому на H3. Це Node.js, не браузер. Тут є process.env, useRuntimeConfig(), сховище Nitro. Але немає window, document і жодної Vue-реактивності. Плутанина між цими середовищами призводить до помилок, які важко відстежити.
Типові помилки
1. Забутий await для readBody
const body = readBody(event) // Баг: це Promise
const body = await readBody(event) // Правильно2. Ручна перевірка методу замість окремих файлів
// Працює, але не рекомендується
export default defineEventHandler(async (event) => {
if (event.method === 'GET') { ... }
if (event.method === 'POST') { ... }
})Краще створити users.get.ts і users.post.ts. Кожен файл менший, зручніший для тестування, і TypeScript точніше визначає типи для кожного методу.
3. Голий Error замість createError
throw new Error('Not found') // Неправильно: статус 500, витікає стек
throw createError({ statusCode: 404, message: 'Not found' }) // Правильно4. event.context.params замість getRouterParam
const id = event.context.params?.id // Працює, але не рекомендований H3 спосіб
const id = getRouterParam(event, 'id') // КращеОбидва варіанти зараз працюють. getRouterParam надійніший у вкладених маршрутах.
Виклик роутів зі сторінок Nuxt
З будь-якого компонента або сторінки використовуй useFetch або $fetch:
// pages/users/[id].vue
const { data } = await useFetch(`/api/users/${id}`)useFetch враховує SSR: на сервері викликає обробник напряму, без HTTP-запиту. На клієнті робить справжній запит. Я бачив, як ця поведінка спантеличувала розробників, які профілювали застосунок і не знаходили очікуваного мережевого виклику під час серверного рендерингу.
Питання на співбесіді
Q: Яка різниця між server/api/ і server/routes/?
A: Файли в server/api/ отримують префікс /api автоматично. Файли в server/routes/ стають будь-яким URL без цього префікса. Використовуй server/routes/ для вебхуків, /sitemap.xml або ендпоінтів, які не повинні бути під /api.
Q: Як ділити логіку між кількома API роутами?
A: Виноси у функцію в server/utils/. Nitro автоматично імпортує файли з цієї папки, тому викликаєш функцію в обробнику без явного import.
Q: Як захистити API роути аутентифікацією?
A: Створи файл у server/middleware/, який перевіряє токен і кладе користувача в event.context.user. Обробник маршруту читає event.context.user без повторної перевірки.
Q: Як повернути власний HTTP-статус, наприклад 201?
A: Виклич setResponseStatus(event, 201) перед поверненням відповіді. Для помилок передавай статус-код безпосередньо в createError({ statusCode: 422, message: '...' }).
Приклади
Мінімальний GET ендпоінт
// server/api/ping.get.ts
export default defineEventHandler(() => {
return { status: 'ok', timestamp: Date.now() }
})
// GET /api/ping → { status: 'ok', timestamp: 1720000000000 }Тут немає async, бо немає I/O. Повернутий об'єкт Nitro автоматично серіалізує в JSON.
POST ендпоінт з валідацією тіла
// server/api/users.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody<{ name: string; email: string }>(event)
if (!body.name || !body.email) {
throw createError({
statusCode: 422,
message: 'name і email обовʼязкові',
})
}
const user = await db.users.create({ data: body })
return user
})Дженерик у readBody<T> каже TypeScript яку форму має body, тому body.name і body.email мають автодоповнення і перевірку типів по всьому обробнику.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.