JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Поддержка HTTP-методов: GET, POST, PUT, DELETE

Поддержка HTTP-методов: GET, POST, PUT, DELETE

Модуль 4: Node.js, Next.js и Angular
12 уровень , 1 лекция
Открыта

1. Введение

В Next.js 15, если вы работаете с App Router, Route Handlers позволяют описывать обработчики для каждого из HTTP-методов прямо в одном файле. Это выглядит очень элегантно: вы экспортируете отдельные асинхронные функции с именами в верхнем регистре — GET, POST, PUT, DELETE.

Базовый шаблон Route Handler


// app/api/todos/route.ts

export async function GET(request) {
  // обработка GET-запроса
}

export async function POST(request) {
  // обработка POST-запроса
}

export async function PUT(request) {
  // обработка PUT-запроса
}

export async function DELETE(request) {
  // обработка DELETE-запроса
}

Каждая функция будет вызвана только тогда, когда клиент отправит запрос с соответствующим методом. То есть, если браузер делает fetch('/api/todos', { method: 'POST' }), будет вызвана функция POST.

Интересный факт:
В Next.js 13/14 Route Handlers были доступны только в папке /app/api/, а начиная с Next.js 15 — их можно использовать и для других маршрутов, не только внутри API.

2. Пример: Простое CRUD API для задач

Давайте сделаем мини-API для управления списком задач (todos). Для простоты будем хранить данные в массиве в памяти (в реальном проекте — в базе данных, но для понимания API этого достаточно).

Шаг 1: Файл Route Handler

Создайте файл app/api/todos/route.ts:


// app/api/todos/route.ts

let todos = [
  { id: 1, text: 'Купить хлеб', done: false },
  { id: 2, text: 'Выучить Next.js', done: false }
];

// Получить список задач
export async function GET(request) {
  return Response.json(todos);
}

// Добавить новую задачу
export async function POST(request) {
  const body = await request.json();
  const newTodo = {
    id: Date.now(),
    text: body.text,
    done: false
  };
  todos.push(newTodo);
  return Response.json(newTodo, { status: 201 });
}

// Обновить задачу (например, отметить как выполненную)
export async function PUT(request) {
  const body = await request.json();
  const todo = todos.find(t => t.id === body.id);
  if (!todo) {
    return Response.json({ error: 'Not found' }, { status: 404 });
  }
  todo.text = body.text ?? todo.text;
  todo.done = body.done ?? todo.done;
  return Response.json(todo);
}

// Удалить задачу
export async function DELETE(request) {
  const { id } = await request.json();
  const index = todos.findIndex(t => t.id === id);
  if (index === -1) {
    return Response.json({ error: 'Not found' }, { status: 404 });
  }
  const deleted = todos.splice(index, 1)[0];
  return Response.json(deleted);
}

Разбор кода

  • GET возвращает весь массив задач.
  • POST ожидает тело запроса с полем text, создает новую задачу и возвращает её.
  • PUT ищет задачу по id и обновляет её поля.
  • DELETE ищет задачу по id и удаляет её из массива.

В реальности: Если бы сервер перезапускался, все задачи бы пропадали, потому что массив заново инициализируется. Для постоянного хранения нужен бы был файл или база данных.

3. Как отправлять запросы с разными методами

С помощью браузера вы можете отправить только GET (обычная ссылка или загрузка страницы). Для остальных методов — используйте JavaScript (например, fetch), Postman или curl.

Примеры fetch-запросов

Получить список задач (GET)


fetch('/api/todos')
  .then(res => res.json())
  .then(data => console.log(data));

Добавить задачу (POST)


fetch('/api/todos', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ text: 'Сделать домашку' })
})
  .then(res => res.json())
  .then(data => console.log(data));

Обновить задачу (PUT)


fetch('/api/todos', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ id: 1, done: true })
})
  .then(res => res.json())
  .then(data => console.log(data));

Удалить задачу (DELETE)


fetch('/api/todos', {
  method: 'DELETE',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ id: 1 })
})
  .then(res => res.json())
  .then(data => console.log(data));

Шутка программиста:
"DELETE-запросы — как удаление файлов на рабочем столе: иногда кажется, что удалил, а оно просто переехало в корзину!"
(В нашем случае — если сервер перезапустить, всё вернётся!)

4. Полезные нюансы

Как Next.js выбирает обработчик по методу

Запрос приходит на URL /api/todos:

  • Если это GET — вызывается функция GET.
  • Если POST — вызывается POST.
  • Если PUT — вызывается PUT.
  • Если DELETE — вызывается DELETE.

Если вы не определили функцию для какого-то метода (например, PATCH), сервер вернёт ошибку 405 Method Not Allowed. Это удобно: вы явно указываете, что поддерживаете, а что нет.

Аргумент request: что внутри?

В каждую функцию приходит объект request — это стандартный Request из Fetch API. В нём есть:

  • request.method — метод запроса (GET, POST и т.д.)
  • request.url — полный URL запроса
  • request.headers — заголовки
  • request.json() — асинхронный метод для получения тела запроса как объекта (только если Content-Type: application/json)
  • request.text() — получить тело как строку

Полезный совет:
Не забывайте, что чтение тела (request.json()) — асинхронное и может быть вызвано только один раз за запрос.

Response и его методы

В Route Handler вы всегда возвращаете экземпляр Response (или используете удобную обёртку Response.json). Это позволяет:

  • Установить статус ответа (например, 200, 201, 404, 500)
  • Вернуть данные в формате JSON, текст или другой контент
  • Указать заголовки (например, CORS, Content-Type)

return new Response('Hello!', { status: 200, headers: { 'X-Custom': 'value' } });

Для JSON-ответа используйте удобную функцию:


return Response.json({ message: 'ok' }, { status: 201 });

Как обрабатывать параметры запроса

Если ваш API работает с коллекцией (например, /api/todos), часто нужно работать с конкретным элементом (например, /api/todos/42). Для этого создают динамические сегменты маршрута ([id]/route.ts). Но если вы хотите поддерживать "универсальный" обработчик, можно передавать id в теле запроса (как выше), либо через query-параметры.

Пример: получение id из query


export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const id = searchParams.get('id');
  if (!id) {
    return Response.json(todos);
  }
  const todo = todos.find(t => t.id === Number(id));
  if (!todo) {
    return Response.json({ error: 'Not found' }, { status: 404 });
  }
  return Response.json(todo);
}

5. Типичные ошибки при работе с HTTP-методами в Route Handlers

Ошибка №1: Не реализован нужный метод.
Если вы забыли экспортировать функцию, например, PUT, а клиент отправляет PUT-запрос, сервер вернёт 405 Method Not Allowed. Проверьте, что экспортируете все нужные методы.

Ошибка №2: Неправильная обработка тела запроса.
Если вы не вызываете await request.json(), а сразу пытаетесь читать поля (request.body.text), получите undefined или ошибку. Используйте только асинхронные методы (await request.json(), await request.text()).

Ошибка №3: Ошибка типов данных.
Если клиент отправляет не JSON, а, например, form-data, а вы вызываете request.json(), получите ошибку парсинга. Всегда проверяйте Content-Type или используйте try/catch.

Ошибка №4: Не возвращаете статус ответа.
Если всегда возвращаете 200 OK, даже при ошибках (например, не найден id), клиенту сложно понять, что что-то пошло не так. Используйте правильные коды: 201 для создания, 404 для "не найдено", 400 для "плохой запрос".

Ошибка №5: Использование одного Route Handler для всех операций.
Иногда проще разделить обработчики по разным файлам (например, для /api/todos и /api/todos/[id]), чтобы не усложнять логику внутри одного файла.

Ошибка №6: Неочевидная мутация данных.
В нашем примере массив todos живёт только в памяти. В реальных приложениях нужно использовать базы данных или хотя бы файлы, чтобы данные не терялись при перезапуске сервера.

Ошибка №7: Не обрабатываете асинхронные ошибки.
Если при чтении тела запроса или в логике возникает ошибка, не забывайте возвращать пользователю информативное сообщение и правильный статус.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ