Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як створити API роути в Nuxt?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Nuxt API роути** - серверні ендпоінти в `server/api/`, що автоматично стають `/api/*` URL. Загорни обробник у `defineEventHandler` і Nuxt сам подбає про маршрутизацію. ```typescript // server/api/hello.get.ts export default defineEventHandler(() => ({ message: 'hello' })) // GET /api/hello → { message: 'hello' } ``` **Ключове:** шлях файлу = URL, налаштовувати роутер не потрібно.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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.ts` → `GET /api/users/:id` - `server/api/users/[id].delete.ts` → `DELETE /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` мають автодоповнення і перевірку типів по всьому обробнику.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.