Skip to main content

Як створити 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: '...' })

Швидкий приклад

typescript
// 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/users
  • server/api/users/index.ts/api/users
  • server/api/users/[id].ts/api/users/:id (всі методи)
  • server/api/users/[id].get.tsGET /api/users/:id
  • server/api/users/[id].delete.tsDELETE /api/users/:id

Квадратні дужки [id] створюють динамічний сегмент. Кілька параметрів в одному шляху теж підтримуються: [userId]/posts/[postId].ts.

Читання даних запиту

Три хелпери покривають більшість ситуацій:

typescript
// 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

typescript
const body = readBody(event) // Баг: це Promise const body = await readBody(event) // Правильно

2. Ручна перевірка методу замість окремих файлів

typescript
// Працює, але не рекомендується export default defineEventHandler(async (event) => { if (event.method === 'GET') { ... } if (event.method === 'POST') { ... } })

Краще створити users.get.ts і users.post.ts. Кожен файл менший, зручніший для тестування, і TypeScript точніше визначає типи для кожного методу.

3. Голий Error замість createError

typescript
throw new Error('Not found') // Неправильно: статус 500, витікає стек throw createError({ statusCode: 404, message: 'Not found' }) // Правильно

4. event.context.params замість getRouterParam

typescript
const id = event.context.params?.id // Працює, але не рекомендований H3 спосіб const id = getRouterParam(event, 'id') // Краще

Обидва варіанти зараз працюють. getRouterParam надійніший у вкладених маршрутах.

Виклик роутів зі сторінок Nuxt

З будь-якого компонента або сторінки використовуй useFetch або $fetch:

typescript
// 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 ендпоінт

typescript
// 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 ендпоінт з валідацією тіла

typescript
// 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 мають автодоповнення і перевірку типів по всьому обробнику.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?