JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Обработка ошибок, возврат HTTP-статусов и сообщений

Обработка ошибок, возврат HTTP-статусов и сообщений

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

1. Базовые HTTP-статусы: что возвращать и когда

Хорошая обработка ошибок — это когда ваш API всегда возвращает понятный и ожидаемый ответ, даже если что-то пошло не так. Это позволяет:

  • Клиенту (браузеру, мобильному приложению) корректно реагировать на ошибку.
  • Разработчику быстро находить и исправлять баги.
  • Пользователю получать дружелюбные сообщения (а не "сервер упал, держитесь!").

В мире HTTP есть целая система кодов статусов. Вот самые важные для REST API (и не только):

Код Название Когда использовать
200
OK
Всё хорошо, успешный ответ
201
Created
Успешно создан новый ресурс
204
No Content
Успешно, но без тела (например, DELETE)
400
Bad Request
Неправильные данные от клиента
401
Unauthorized
Необходима авторизация
403
Forbidden
Нет прав на действие
404
Not Found
Ресурс не найден
409
Conflict
Конфликт (например, дублирование)
422
Unprocessable Entity
Ошибка валидации данных
500
Internal Server Error
Неожиданная ошибка на сервере

Важно:
Старайтесь возвращать именно тот статус, который отражает суть проблемы. Если пользователь отправил некорректные данные — это 400 или 422, если сервер не смог обработать запрос из-за ошибки в коде — это 500.

2. Как возвращать статус-коды и сообщения в Next.js Route Handlers

В Next.js (App Router, Route Handlers) для формирования ответа используется объект NextResponse. Он позволяет не только отправлять тело ответа (JSON, текст), но и устанавливать статус-код, заголовки и т.д.

Простейший пример


import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json(
    { message: 'Всё хорошо!' }, // тело ответа
    { status: 200 }             // статус-код
  );
}

Пример с ошибкой

export async function GET() {
  return NextResponse.json(
    { error: 'Ресурс не найден' },
    { status: 404 }
  );
}

Пример с валидацией

export async function POST(request) {
  const body = await request.json();

  if (!body.name || typeof body.name !== 'string') {
    return NextResponse.json(
      { error: 'Поле name обязательно и должно быть строкой' },
      { status: 400 }
    );
  }

  // ...дальше логика создания ресурса

  return NextResponse.json({ message: 'Создано!' }, { status: 201 });
}

3. Универсальная схема обработки ошибок: try/catch

В реальной жизни ошибки могут случаться где угодно: не только при валидации, но и при работе с базой данных, сторонними API, файловой системой и т.д. Чтобы ловить такие ошибки, используйте try/catch.

Пример с try/catch

export async function GET() {
  try {
    // Здесь может быть сложная логика: запрос к БД, файловой системе и т.д.
    throw new Error('Что-то пошло не так!');

    // Если всё хорошо:
    // return NextResponse.json({ data: ... }, { status: 200 });
  } catch (err) {
    // Логируем ошибку для себя (можно в консоль, можно в Sentry и т.п.)
    console.error('Ошибка сервера:', err);

    // Возвращаем клиенту понятное сообщение и статус 500
    return NextResponse.json(
      { error: 'Внутренняя ошибка сервера' },
      { status: 500 }
    );
  }
}

Важно!
Никогда не отправляйте клиенту текст настоящей ошибки (err.message), если не уверены, что там нет чувствительных данных. Лучше возвращать "человеческое" сообщение, а детали ошибки логировать на сервере.

4. Типовые сценарии: примеры для CRUD API

Давайте разберём несколько типовых кейсов для REST API: чтение, создание, обновление, удаление.

Получение ресурса (GET)

export async function GET(request, { params }) {
  const { id } = params;

  // Допустим, мы ищем пользователя по id
  const user = await findUserById(id);

  if (!user) {
    return NextResponse.json(
      { error: 'Пользователь не найден' },
      { status: 404 }
    );
  }

  return NextResponse.json(user, { status: 200 });
}

Создание ресурса (POST) с валидацией

export async function POST(request) {
  try {
    const body = await request.json();

    // Примитивная валидация
    if (!body.email || !body.password) {
      return NextResponse.json(
        { error: 'Email и пароль обязательны' },
        { status: 400 }
      );
    }

    // Проверяем, не занят ли email
    const exists = await findUserByEmail(body.email);
    if (exists) {
      return NextResponse.json(
        { error: 'Пользователь с таким email уже существует' },
        { status: 409 }
      );
    }

    // Создаём пользователя
    const newUser = await createUser(body);

    return NextResponse.json(newUser, { status: 201 });
  } catch (err) {
    console.error('Ошибка при создании пользователя:', err);
    return NextResponse.json(
      { error: 'Не удалось создать пользователя' },
      { status: 500 }
    );
  }
}

Обновление и удаление (PUT/PATCH/DELETE)

Принцип тот же: валидируем входные данные, проверяем, существует ли объект, и ловим все неожиданные ошибки.

5. Формат сообщений об ошибках: best practices

Какой формат лучше всего использовать?

Обычно удобнее всего возвращать JSON с ключом error (или message), чтобы клиенту было удобно парсить ответ. Можно добавить и дополнительную информацию (например, код ошибки, поле, к которому относится ошибка).

Стандартный вариант:

{
  "error": "Пользователь не найден"
}

Более подробный вариант:

{
  "error": "Ошибка валидации",
  "details": [
    { "field": "email", "message": "Неверный формат email" }
  ]
}

Если используете валидацию через zod или другую библиотеку, обычно она возвращает массив ошибок — это удобно для фронтенда, чтобы отобразить пользователю все проблемы разом.

6. Практика: добавляем обработку ошибок в мини-приложение

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

// app/api/todos/route.js
import { NextResponse } from 'next/server';

let todos = [];

export async function POST(request) {
  try {
    const body = await request.json();

    if (!body.title || typeof body.title !== 'string') {
      return NextResponse.json(
        { error: 'Поле title обязательно и должно быть строкой' },
        { status: 400 }
      );
    }

    // Проверим, нет ли уже такой задачи
    if (todos.some(todo => todo.title === body.title)) {
      return NextResponse.json(
        { error: 'Такая задача уже существует' },
        { status: 409 }
      );
    }

    const newTodo = { id: todos.length + 1, title: body.title };
    todos.push(newTodo);

    return NextResponse.json(newTodo, { status: 201 });
  } catch (err) {
    console.error('Ошибка при добавлении задачи:', err);
    return NextResponse.json(
      { error: 'Не удалось добавить задачу' },
      { status: 500 }
    );
  }
}

7. Как обрабатывать ошибки на клиенте

Клиенту (например, вашему React-приложению) важно правильно реагировать на статусы и сообщения:

const response = await fetch('/api/todos', {
  method: 'POST',
  body: JSON.stringify({ title: '' }),
  headers: { 'Content-Type': 'application/json' }
});

if (!response.ok) {
  const data = await response.json();
  alert(`Ошибка: ${data.error}`);
} else {
  const todo = await response.json();
  // Обновляем список задач
}

8. Типичные ошибки при обработке ошибок и статус-кодов

Ошибка №1: всегда возвращается 200 OK, даже если произошла ошибка.
Это сбивает с толку клиента: он думает, что всё хорошо, а на самом деле нет. Всегда ставьте правильный статус-код!

Ошибка №2: отправка технической информации клиенту.
Никогда не возвращайте клиенту stack trace или подробности внутренней ошибки — это может быть небезопасно.

Ошибка №3: отсутствие try/catch вокруг асинхронного кода.
Если не обернуть код в try/catch, ошибка "упадёт" наружу, а клиент получит 500 без объяснения.

Ошибка №4: не логируются ошибки на сервере.
Если не писать ошибки в консоль (или в систему логирования), вы не узнаете, что что-то пошло не так.

Ошибка №5: не возвращается понятное сообщение об ошибке.
Пользователь не понимает, что случилось, и пишет гневные письма в поддержку.

Ошибка №6: неверный статус-код для ошибки валидации.
Например, возвращают 500 вместо 400 или 422 — это мешает фронтенду корректно обрабатывать ошибки.

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