JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Примеры API-эндпоинтов в Next.js 15

Примеры API-эндпоинтов в Next.js 15

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

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)).

3
Опрос
Route Handlers, 12 уровень, 4 лекция
Недоступен
Route Handlers
Route Handlers
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ