1. Введение
API-эндпоинт — это файл в папке app/api/, который экспортирует асинхронную функцию для обработки HTTP-запроса определённого типа (GET, POST и т.д.). Каждый такой файл — это отдельный обработчик, который можно вызывать с фронтенда или внешнего клиента.
Пример структуры:
app/
api/
products/
route.js
users/
route.js
tasks/
route.js
Каждый файл route.js (или route.ts) — отдельный эндпоинт, который можно вызвать по адресу /api/products, /api/users и т.д.
Самый простой GET-эндпоинт
Начнём с классики: сделаем эндпоинт, который возвращает список продуктов.
Файл: app/api/products/route.js
// app/api/products/route.js
import { NextResponse } from 'next/server';
const products = [
{ id: 1, name: 'Шоколад', price: 120 },
{ id: 2, name: 'Сыр', price: 250 },
{ id: 3, name: 'Хлеб', price: 55 },
];
// Обработка GET-запроса
export async function GET(request) {
// Можно добавить логирование или фильтрацию, если нужно
return NextResponse.json(products);
}
Теперь, если отправить GET-запрос на /api/products, в ответ придёт JSON со всеми продуктами.
Пример ответа:
[
{ "id": 1, "name": "Шоколад", "price": 120 },
{ "id": 2, "name": "Сыр", "price": 250 },
{ "id": 3, "name": "Хлеб", "price": 55 }
]
Эндпоинт с параметрами: фильтрация через query
Допустим, мы хотим возвращать не все продукты, а только те, у которых цена выше определённого значения. Для этого будем читать query-параметр.
Пример запроса:
/api/products?minPrice=100
Изменим Route Handler:
export async function GET(request) {
const { searchParams } = new URL(request.url);
const minPrice = Number(searchParams.get('minPrice')) || 0;
const filtered = products.filter(p => p.price >= minPrice);
return NextResponse.json(filtered);
}
Теперь, если клиент передаст параметр minPrice, ответ будет содержать только подходящие продукты.
2. POST-эндпоинт: добавляем новый продукт
Теперь реализуем добавление нового продукта. Для простоты будем хранить данные в памяти (в реальном приложении — в базе данных).
Файл: app/api/products/route.js
let products = [
{ id: 1, name: 'Шоколад', price: 120 },
{ id: 2, name: 'Сыр', price: 250 },
{ id: 3, name: 'Хлеб', price: 55 },
];
export async function POST(request) {
const body = await request.json();
// Простая валидация
if (!body.name || typeof body.price !== 'number') {
return NextResponse.json(
{ error: 'Некорректные данные: name и price обязательны' },
{ status: 400 }
);
}
const newProduct = {
id: products.length + 1,
name: body.name,
price: body.price,
};
products.push(newProduct);
// Возвращаем созданный продукт и статус 201 Created
return NextResponse.json(newProduct, { status: 201 });
}
Пример тела запроса (JSON):
{
"name": "Молоко",
"price": 80
}
Ответ:
{
"id": 4,
"name": "Молоко",
"price": 80
}
3. Эндпоинт с динамическим параметром: получение продукта по id
Иногда нужно получить конкретный продукт по его id. Для этого создадим динамический сегмент:
Файл: app/api/products/[id]/route.js
import { NextResponse } from 'next/server';
const products = [
{ id: 1, name: 'Шоколад', price: 120 },
{ id: 2, name: 'Сыр', price: 250 },
{ id: 3, name: 'Хлеб', price: 55 },
];
export async function GET(request, { params }) {
const id = Number(params.id);
const product = products.find(p => p.id === id);
if (!product) {
return NextResponse.json({ error: 'Продукт не найден' }, { status: 404 });
}
return NextResponse.json(product);
}
Теперь, если обратиться к /api/products/2, в ответ придёт:
{ "id": 2, "name": "Сыр", "price": 250 }
Если продукта нет — будет ошибка 404.
4. PUT и DELETE: обновление и удаление продукта
В реальных API часто нужно не только добавлять, но и менять или удалять объекты.
Файл: app/api/products/[id]/route.js
let products = [
{ id: 1, name: 'Шоколад', price: 120 },
{ id: 2, name: 'Сыр', price: 250 },
{ id: 3, name: 'Хлеб', price: 55 },
];
export async function PUT(request, { params }) {
const id = Number(params.id);
const body = await request.json();
const product = products.find(p => p.id === id);
if (!product) {
return NextResponse.json({ error: 'Продукт не найден' }, { status: 404 });
}
if (body.name) product.name = body.name;
if (typeof body.price === 'number') product.price = body.price;
return NextResponse.json(product);
}
export async function DELETE(request, { params }) {
const id = Number(params.id);
const index = products.findIndex(p => p.id === id);
if (index === -1) {
return NextResponse.json({ error: 'Продукт не найден' }, { status: 404 });
}
const deleted = products.splice(index, 1)[0];
return NextResponse.json(deleted);
}
Теперь можно отправить PUT-запрос на /api/products/2 с новым именем или ценой, или DELETE-запрос — чтобы удалить продукт.
5. Пример: API для списка задач (ToDo)
Давайте создадим мини-API для задач, чтобы закрепить материал.
Файл: app/api/tasks/route.js
import { NextResponse } from 'next/server';
let tasks = [
{ id: 1, text: 'Купить хлеб', done: false },
{ id: 2, text: 'Сделать домашку', done: false },
];
// Получить все задачи
export async function GET() {
return NextResponse.json(tasks);
}
// Добавить задачу
export async function POST(request) {
const body = await request.json();
if (!body.text) {
return NextResponse.json({ error: 'Текст задачи обязателен' }, { status: 400 });
}
const newTask = {
id: tasks.length + 1,
text: body.text,
done: false,
};
tasks.push(newTask);
return NextResponse.json(newTask, { status: 201 });
}
Файл: app/api/tasks/[id]/route.js
let tasks = [
{ id: 1, text: 'Купить хлеб', done: false },
{ id: 2, text: 'Сделать домашку', done: false },
];
// Получить одну задачу
export async function GET(request, { params }) {
const id = Number(params.id);
const task = tasks.find(t => t.id === id);
if (!task) {
return NextResponse.json({ error: 'Задача не найдена' }, { status: 404 });
}
return NextResponse.json(task);
}
// Обновить задачу (например, отметить как выполненную)
export async function PUT(request, { params }) {
const id = Number(params.id);
const body = await request.json();
const task = tasks.find(t => t.id === id);
if (!task) {
return NextResponse.json({ error: 'Задача не найдена' }, { status: 404 });
}
if (typeof body.done === 'boolean') task.done = body.done;
if (body.text) task.text = body.text;
return NextResponse.json(task);
}
// Удалить задачу
export async function DELETE(request, { params }) {
const id = Number(params.id);
const index = tasks.findIndex(t => t.id === id);
if (index === -1) {
return NextResponse.json({ error: 'Задача не найдена' }, { status: 404 });
}
const deleted = tasks.splice(index, 1)[0];
return NextResponse.json(deleted);
}
6. Типичные ошибки при написании API-эндпоинтов
Ошибка №1: забыли сделать данные изменяемыми.
Если вы используете const для массива, попытка изменить его вызовет ошибку. Для хранения изменяемых данных используйте let.
Ошибка №2: не проверили тело запроса.
Если не делать валидацию, можно получить ошибку или сохранить некорректные данные. Всегда проверяйте, что пришло в request.json().
Ошибка №3: неправильный статус-код.
Не отправляйте 200 OK, если произошла ошибка! Для ошибок используйте коды 400, 404, 500 и т.д.
Ошибка №4: не обрабатываете исключения.
Если в коде случается ошибка (например, валидация не прошла), возвращайте понятный JSON с ошибкой, а не просто "Internal Server Error".
Ошибка №5: забыли про асинхронность.
Все функции-обработчики должны быть async. Не забывайте ставить await перед асинхронными операциями (например, чтение тела запроса).
Ошибка №6: не обрабатываете динамические параметры.
Если вы создаёте файл с [id], не забывайте приводить параметр к нужному типу (например, к числу через Number(params.id)).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ