Пошук уроків, статей та іншого контенту
Реалізуйте обробники GET, POST, PUT, PATCH і DELETE та визначте призначення кожного HTTP-методу.
Route Handler — це файл route.js або route.ts у папці app, який обробляє HTTP-запити. Назва експортованої функції визначає HTTP-метод:
GET — отримати дані;
POST — створити новий ресурс;
PUT — повністю замінити ресурс;
PATCH — частково оновити ресурс;
DELETE — видалити ресурс.
Наприклад, створимо обробник для завдань:
app/
└── api/
└── tasks/
└── route.jsПісля цього обробник буде доступний за адресою:
/api/tasksУ файлі route.js потрібно експортувати асинхронну функцію для кожного методу, який має підтримувати API:
export async function GET(request) {
// Обробка GET-запиту
}
export async function POST(request) {
// Обробка POST-запиту
}Якщо для певного методу немає відповідної функції, Route Handler зазвичай не підтримує цей метод.
Метод GET використовують для отримання даних. Він не повинен змінювати стан сервера.
Наприклад, GET /api/tasks може повернути список завдань, а GET /api/tasks?id=1 — одне завдання.
Метод POST використовують для створення нового ресурсу.
Дані зазвичай надсилають у форматі JSON у тілі запиту. У Route Handler тіло запиту можна прочитати за допомогою:
const body = await request.json();Для успішного створення ресурсу зазвичай повертають статус 201 Created.
Метод PUT використовують для повної заміни ресурсу.
Наприклад, якщо завдання має поля title і completed, запит PUT має передати обидва поля. Відсутнє поле може бути замінене значенням за замовчуванням або призвести до помилки валідації.
Метод PATCH використовують для часткового оновлення ресурсу.
Якщо потрібно змінити лише поле completed, не обов’язково надсилати весь об’єкт:
{
"completed": true
}Метод DELETE використовують для видалення ресурсу.
Після успішного видалення сервер може повернути статус 204 No Content, якщо у відповіді немає тіла.
Створіть файл app/api/tasks/route.js:
import { NextResponse } from "next/server";
let tasks = [
{
id: 1,
title: "Вивчити Route Handlers",
completed: false,
},
{
id: 2,
title: "Написати перший API-запит",
completed: true,
},
];
let nextId = 3;
function getTaskId(request) {
const url = new URL(request.url);
const id = url.searchParams.get("id");
if (id === null) {
return null;
}
const taskId = Number(id);
if (!Number.isInteger(taskId) || taskId < 1) {
return undefined;
}
return taskId;
}
export async function GET(request) {
const taskId = getTaskId(request);
if (taskId === undefined) {
return NextResponse.json(
{ error: "Параметр id має бути додатним цілим числом" },
{ status: 400 }
);
}
if (taskId === null) {
return NextResponse.json(tasks);
}
const task = tasks.find((item) => item.id === taskId);
if (!task) {
return NextResponse.json(
{ error: "Завдання не знайдено" },
{ status: 404 }
);
}
return NextResponse.json(task);
}
export async function POST(request) {
let body;
try {
body = await request.json();
} catch {
return NextResponse.json(
{ error: "Тіло запиту має бути коректним JSON" },
{ status: 400 }
);
}
if (typeof body.title !== "string" || body.title.trim() === "") {
return NextResponse.json(
{ error: "Поле title є обов'язковим" },
{ status: 400 }
);
}
const task = {
id: nextId,
title: body.title.trim(),
completed: Boolean(body.completed),
};
nextId += 1;
tasks.push(task);
return NextResponse.json(task, { status: 201 });
}
export async function PUT(request) {
const taskId = getTaskId(request);
if (taskId === undefined || taskId === null) {
return NextResponse.json(
{ error: "Для PUT потрібно передати коректний параметр id" },
{ status: 400 }
);
}
const taskIndex = tasks.findIndex((item) => item.id === taskId);
if (taskIndex === -1) {
return NextResponse.json(
{ error: "Завдання не знайдено" },
{ status: 404 }
);
}
let body;
try {
body = await request.json();
} catch {
return NextResponse.json(
{ error: "Тіло запиту має бути коректним JSON" },
{ status: 400 }
);
}
if (
typeof body.title !== "string" ||
body.title.trim() === "" ||
typeof body.completed !== "boolean"
) {
return NextResponse.json(
{ error: "PUT вимагає поля title і completed" },
{ status: 400 }
);
}
const updatedTask = {
id: taskId,
title: body.title.trim(),
completed: body.completed,
};
tasks[taskIndex] = updatedTask;
return NextResponse.json(updatedTask);
}
export async function PATCH(request) {
const taskId = getTaskId(request);
if (taskId === undefined || taskId === null) {
return NextResponse.json(
{ error: "Для PATCH потрібно передати коректний параметр id" },
{ status: 400 }
);
}
const taskIndex = tasks.findIndex((item) => item.id === taskId);
if (taskIndex === -1) {
return NextResponse.json(
{ error: "Завдання не знайдено" },
{ status: 404 }
);
}
let body;
try {
body = await request.json();
} catch {
return NextResponse.json(
{ error: "Тіло запиту має бути коректним JSON" },
{ status: 400 }
);
}
if ("title" in body && typeof body.title !== "string") {
return NextResponse.json(
{ error: "Поле title має бути рядком" },
{ status: 400 }
);
}
if ("completed" in body && typeof body.completed !== "boolean") {
return NextResponse.json(
{ error: "Поле completed має бути булевим значенням" },
{ status: 400 }
);
}
if (!("title" in body) && !("completed" in body)) {
return NextResponse.json(
{ error: "Потрібно передати хоча б одне поле для оновлення" },
{ status: 400 }
);
}
const updatedTask = {
...tasks[taskIndex],
...(body.title !== undefined && { title: body.title.trim() }),
...(body.completed !== undefined && { completed: body.completed }),
};
tasks[taskIndex] = updatedTask;
return NextResponse.json(updatedTask);
}
export async function DELETE(request) {
const taskId = getTaskId(request);
if (taskId === undefined || taskId === null) {
return NextResponse.json(
{ error: "Для DELETE потрібно передати коректний параметр id" },
{ status: 400 }
);
}
const taskIndex = tasks.findIndex((item) => item.id === taskId);
if (taskIndex === -1) {
return NextResponse.json(
{ error: "Завдання не знайдено" },
{ status: 404 }
);
}
tasks.splice(taskIndex, 1);
return new Response(null, { status: 204 });
}У цьому прикладі дані зберігаються в масиві tasks. Це зручно для навчання, але після перезапуску сервера всі зміни буде втрачено. У реальному застосунку Route Handler зазвичай працює з базою даних або іншим постійним сховищем.
Отримати всі завдання:
curl http://localhost:3000/api/tasksОтримати одне завдання:
curl "http://localhost:3000/api/tasks?id=1"Створити завдання:
curl -X POST http://localhost:3000/api/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Створити API","completed":false}'Повністю замінити завдання:
curl -X PUT "http://localhost:3000/api/tasks?id=1" \
-H "Content-Type: application/json" \
-d '{"title":"Оновлена назва","completed":true}'Частково оновити завдання:
curl -X PATCH "http://localhost:3000/api/tasks?id=1" \
-H "Content-Type: application/json" \
-d '{"completed":false}'Видалити завдання:
curl -X DELETE "http://localhost:3000/api/tasks?id=1"Route Handler може повертати різні HTTP-статуси залежно від результату операції:
200 OK — запит успішно виконано;
201 Created — ресурс успішно створено;
204 No Content — операцію виконано, але тіло відповіді відсутнє;
400 Bad Request — запит містить некоректні дані;
404 Not Found — ресурс не знайдено.
Для JSON-відповідей зручно використовувати NextResponse.json():
return NextResponse.json(
{ error: "Некоректні дані" },
{ status: 400 }
);Route Handler повинен називатися route.js або route.ts і знаходитися всередині app:
app/api/tasks/route.jsФайл із назвою handler.js або api.js Next.js не сприйматиме як Route Handler.
Content-TypeЯкщо клієнт надсилає JSON, запит повинен містити заголовок:
Content-Type: application/jsonБез нього серверу складніше правильно визначити формат тіла запиту.
request.json() у GETМетод GET зазвичай не використовує тіло запиту. Параметри для отримання ресурсу можна передати в URL:
/api/tasks?id=1Прочитати їх можна через request.url і URLSearchParams.
PUT замінює весь ресурс;
PATCH змінює лише передані поля.
Якщо потрібно змінити тільки один атрибут, використовуйте PATCH.
Після return NextResponse.json(...) функція завершується. Перевіряйте, щоб кожна помилка повертала відповідь і подальший код не продовжував виконання з некоректними даними.
Route Handler створюють у файлі route.js або route.ts всередині папки app.
Назва експортованої функції відповідає HTTP-методу.
GET отримує дані.
POST створює ресурс.
PUT повністю замінює ресурс.
PATCH частково оновлює ресурс.
DELETE видаляє ресурс.
Тіло JSON-запиту читають через await request.json().
Відповіді з JSON формують за допомогою NextResponse.json().
Для якісного API важливо повертати відповідні HTTP-статуси та перевіряти вхідні дані.