Подключаем первый API: пошагово
Теория теорией — но пока вы своими руками не подключите API, оно не «застрянет в голове». Соберём минимальный, но честный пример: серверный API-роут в Next.js, который вызывает OpenAI, корректно обрабатывает ошибки, и понятно сообщает клиенту что не так.
План
Шаг 1 — кладём ключ в .env (OPENAI_API_KEY=sk-proj-...) и проверяем, что .env в .gitignore. Шаг 2 — создаём файл app/api/ask/route.ts с функцией POST. Шаг 3 — внутри функции читаем тело запроса, берём ключ из process.env, шлём fetch на OpenAI. Шаг 4 — возвращаем ответ или вменяемую ошибку. Шаг 5 — на клиенте кнопка вызывает /api/ask, не OpenAI напрямую. Никакой магии.
Что такое route handler
В Next.js App Router любой файл app/api/.../route.ts с экспортируемой функцией POST (или GET) автоматически становится HTTP-роутом. Зашли по адресу /api/ask методом POST — выполнилась эта функция. Внутри она получает Request, должна вернуть Response. Это серверный код — нет window, нет document, можно читать process.env, можно ходить в БД, можно делать ВСЁ что угодно. То же самое в SvelteKit (+server.ts), Remix (action), Express (app.post(...)) — общая идея.
Обработка ошибок
Главная ошибка новичков — забыть про статусы. OpenAI вернул 401? Значит ваш ключ невалидный — скажите клиенту чётко, не «что-то пошло не так». Вернул 429? Превышен лимит — пусть пользователь подождёт. 500? У них упало — попробуйте позже. Каждый класс ошибки → свой код в вашем ответе клиенту: 400 = клиент сделал что-то не так, 401 = не авторизован, 502 = upstream-сервис ответил плохо, 500 = вы накосячили. И всегда console.error на сервере — иначе не отдебажите.
Где смотреть, если не работает
1) Терминал, в котором запущен
там логи серверного кода и ваши console.error. 2) DevTools → Network в браузере: видно запрос /api/ask, его статус, тело ответа. 3) Если 401 — точно ли ключ из дашборда (часто копируют со скрытыми пробелами)? Перезапускали dev-сервер после правки .env? 4) Если CORS-ошибка — вы случайно зовёте OpenAI с клиента, не с сервера. 5) Если ничего не происходит — проверьте, что метод POST, а не GET (типичная ошибка).
// Полный пример: подключаем OpenAI через серверный роут.
// .env
OPENAI_API_KEY=sk-proj-твойреальныйключ
DAILY_REQUEST_LIMIT=50
// app/api/ask/route.ts
import { NextResponse } from 'next/server';
export async function POST(req: Request) {
const { prompt } = await req.json();
if (!prompt || prompt.length < 2) {
return NextResponse.json({ error: 'Промпт пустой' }, { status: 400 });
}
try {
const r = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
}),
});
if (!r.ok) {
const err = await r.text();
console.error('OpenAI error:', r.status, err);
// 401 = неверный ключ
// 429 = превышен лимит
// 500 = проблема на стороне OpenAI
return NextResponse.json(
{ error: `OpenAI ответил ${r.status}` },
{ status: 502 }
);
}
const data = await r.json();
return NextResponse.json({ answer: data.choices[0].message.content });
} catch (e) {
console.error('Network error:', e);
return NextResponse.json({ error: 'Сетевая ошибка' }, { status: 500 });
}
}
// app/page.tsx
'use client';
import { useState } from 'react';
export default function Page() {
const [answer, setAnswer] = useState('');
async function ask() {
const r = await fetch('/api/ask', {
method: 'POST',
body: JSON.stringify({ prompt: 'Объясни что такое API' }),
});
const data = await r.json();
setAnswer(data.answer ?? data.error);
}
return (
<>
<button onClick={ask}>Спросить ИИ</button>
<p>{answer}</p>
</>
);
}Полный рабочий пример: серверный роут + клиентская кнопка