1. Введение
Когда пользователь или клиентское приложение отправляет запрос к вашему API, Next.js передаёт в ваш обработчик объект запроса — Request. Это не тот же самый объект, что в Node.js или Express, а современный объект, основанный на стандартном Web API Request. Аналогично, для ответа вы используете объект Response или, чаще, NextResponse.
Почему это важно?
Потому что теперь вы работаете с API, максимально похожим на браузерный fetch, а не на старый добрый Express. Это делает код переносимым и современным, но иногда добавляет неожиданностей, если вы привыкли к Node.js.
Объект Request: что это такое и зачем нужен
В Route Handlers ваш обработчик получает первым аргументом объект Request. Это инкапсулированная информация о входящем запросе: метод, заголовки, путь, тело, параметры и т.д.
Пример обработчика:
export async function POST(request) {
// здесь request — это объект Request
}
Основные свойства и методы объекта Request
| Свойство/метод | Описание |
|---|---|
| request.method | HTTP-метод запроса (GET, POST, и т.д.) |
| request.headers | Коллекция заголовков запроса |
| request.url | Полный URL запроса |
| request.json() | Асинхронно парсит тело запроса как JSON |
| request.text() | Асинхронно возвращает тело запроса как строку |
| request.formData() | Асинхронно парсит тело как FormData (для форм) |
| request.body | ReadableStream для низкоуровневого чтения тела |
Важно: почти все методы работы с телом запроса (json(), text(), formData()) — асинхронные и могут быть вызваны только ОДИН раз за обработку!
2. Свойства объекта request
request.method: определяем тип запроса
Самое первое, что обычно проверяют в API-обработчике — это метод запроса. Например, чтобы разрешить только POST-запросы, а остальные отвергать.
export async function handler(request) {
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}
// ...дальше логика для POST
}
Зачем это нужно?
API-эндпоинт может быть вызван разными методами, но не все из них должны быть разрешены. Например, на эндпоинт для создания сущности обычно разрешён только POST.
request.headers: работа с заголовками
Заголовки — это метаданные запроса: тип содержимого (Content-Type), авторизация (Authorization), пользовательский агент (User-Agent), и многое другое.
Объект request.headers — это не обычный объект, а экземпляр класса Headers. У него есть методы для получения значений:
export async function POST(request) {
const contentType = request.headers.get('content-type');
const userAgent = request.headers.get('user-agent');
// ...
}
- Названия заголовков нечувствительны к регистру (но лучше писать в нижнем).
- Если заголовок не найден, возвращается null.
Пример: Проверка авторизации
export async function POST(request) {
const auth = request.headers.get('authorization');
if (!auth || auth !== 'Bearer supersecrettoken') {
return new Response('Unauthorized', { status: 401 });
}
// ...логика для авторизованных пользователей
}
3. request.json(): получение данных из тела запроса
Самый частый кейс для POST/PUT-запросов — обработка данных, отправленных с клиента. В Next.js Route Handlers для этого используется метод request.json().
export async function POST(request) {
const data = await request.json();
// Теперь data — это объект, который отправил клиент
// Например: { name: "Alice", age: 30 }
}
- request.json() возвращает промис, поэтому обязательно используйте await.
- Если тело запроса не является корректным JSON — будет ошибка (try/catch вам в помощь!).
- Вызывать методы чтения тела (json(), text(), и т.д.) можно только ОДИН раз за обработку, иначе получите ошибку "body already used".
Типичная структура POST-запроса
// Фронтенд отправляет:
fetch('/api/route', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice', age: 30 })
});
// Бэкенд принимает:
export async function POST(request) {
const body = await request.json();
// body: { name: 'Alice', age: 30 }
}
4. request.text() и request.formData(): альтернативные способы чтения тела
Иногда с клиента отправляют не JSON, а обычный текст или форму (например, через <form> без JS).
- request.text() — возвращает тело запроса как строку.
- request.formData() — возвращает объект FormData (для multipart/form-data или application/x-www-form-urlencoded).
export async function POST(request) {
const text = await request.text();
// или
const form = await request.formData();
const username = form.get('username');
}
5. Объект Response и NextResponse: формируем ответ
В Next.js Route Handlers вы обычно возвращаете либо стандартный объект Response, либо NextResponse (расширение Response с удобствами Next.js).
Простейший ответ:
return new Response('OK');
Ответ с JSON:
return Response.json({ message: 'Hello, world!' });
Ответ с кастомными заголовками и статусом:
return new Response(JSON.stringify({ error: 'Not found' }), {
status: 404,
headers: { 'Content-Type': 'application/json' }
});
NextResponse: зачем нужен?
NextResponse — это расширение стандартного Response, добавляющее дополнительные методы (например, для редиректов, управления cookie и т.д.). Для обычных JSON-ответов можно использовать либо Response, либо NextResponse.
6. Практика: пишем мини-API для задач
Давайте добавим в наше учебное приложение API-эндпоинт для создания задач (todo).
Фронтенд:
// Отправляем новую задачу
fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: 'Купить хлеб', important: true })
});
Бэкенд (app/api/todos/route.js):
let todos = []; // Имитация "базы данных" в памяти
export async function POST(request) {
try {
const data = await request.json();
// Мини-валидация
if (!data.text) {
return Response.json({ error: 'Текст задачи обязателен' }, { status: 400 });
}
const todo = {
id: Date.now(),
text: data.text,
important: !!data.important
};
todos.push(todo);
// Отправим обратно созданную задачу
return Response.json(todo, { status: 201 });
} catch (err) {
return Response.json({ error: 'Некорректный JSON' }, { status: 400 });
}
}
- Используем await request.json() для чтения тела запроса.
- Проверяем обязательные поля.
- Возвращаем созданную задачу с кодом 201 (Created).
- Если ошибка парсинга — возвращаем ошибку 400.
7. Разбор типичных ошибок при работе с request.json(), headers, method
Ошибка №1: Попытка прочитать тело запроса более одного раза.
Очень частая проблема: если вы дважды вызовете await request.json() (или text(), или formData()), получите ошибку "body already used". Всегда сохраняйте результат в переменную и используйте её повторно.
Ошибка №2: Не проверяется Content-Type.
Если клиент отправил не JSON, а вы вызываете request.json(), будет ошибка. Лучше заранее проверить заголовок:
const contentType = request.headers.get('content-type');
if (!contentType?.includes('application/json')) {
return new Response('Content-Type must be application/json', { status: 415 });
}
Ошибка №3: Не обрабатываются ошибки парсинга JSON.
Если в теле запроса некорректный JSON, ваш обработчик упадёт. Используйте try/catch!
Ошибка №4: Не проверяется метод запроса.
Если вы не проверяете request.method и пишете одну функцию для всех методов, можно случайно обработать не тот тип запроса. Лучше явно разделять обработчики (или хотя бы проверять метод внутри).
Ошибка №5: Неправильная работа с заголовками.
Заголовки в объекте Headers возвращают строки или null. Не забывайте обрабатывать случай отсутствия заголовка, иначе получите ошибки типа "Cannot read property 'includes' of null".
Ошибка №6: Возврат "сырых" объектов вместо JSON.
Если вы возвращаете обычный объект через new Response(obj), клиент получит строку [object Object]. Используйте Response.json() или JSON.stringify() и правильный Content-Type.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ