JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Создание REST API для списка задач

Создание REST API для списка задач

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

1. Что такое REST API?

Перед тем как погрузиться в код, давайте коротко вспомним, что вообще такое REST API. REST (Representational State Transfer) — это стиль архитектуры, при котором взаимодействие с сервером происходит через стандартные HTTP-методы (GET, POST, PUT, DELETE и т.д.), а данные передаются обычно в формате JSON. Каждый endpoint (маршрут) отвечает за определённое действие с ресурсом (например, задачей).

В нашем случае ресурс — это "задача" (task). Мы хотим реализовать API, который позволит:

  • Получить список всех задач
  • Получить одну задачу по id
  • Добавить новую задачу
  • Обновить существующую задачу
  • Удалить задачу

Если сравнивать с жизнью — представьте, что вы приходите в библиотеку (сервер), и по разным адресам (endpoint) можете получить список всех книг, взять одну, добавить новую, обновить или выбросить старую (только, пожалуйста, не делайте этого с настоящими книгами).

Минимальная структура Express-приложения

Для начала вспомним, как выглядит минимальное Express-приложение. Мы будем развивать именно это приложение по мере изучения темы:


const express = require('express');
const app = express();
const PORT = 3000;

// Позволяет Express читать JSON из тела запроса
app.use(express.json());

app.listen(PORT, () => {
  console.log(`Сервер запущен на http://localhost:${PORT}`);
});

Всё, что нам нужно — это Express и немного энтузиазма (и, возможно, кофе).

Где будем хранить задачи?

Для простоты мы пока не будем использовать базы данных. Все задачи будут храниться в массиве в памяти. Это не очень надёжно (при перезапуске сервера всё исчезнет), но для тренировки и понимания REST этого достаточно.


let tasks = [
  // Пример задачи
  // { id: 1, title: "Купить хлеб", completed: false }
];

Каждая задача — это объект с полями:

  • id — уникальный идентификатор (целое число)
  • title — текст задачи
  • completed — выполнена ли задача (boolean)

Таблица соответствия методов и маршрутов

Действие HTTP-метод Маршрут Описание
Получить все задачи
GET
/tasks
Вернуть список задач
Получить задачу
GET
/tasks/:id
Вернуть одну задачу
Добавить задачу
POST
/tasks
Добавить новую задачу
Обновить задачу
PUT
/tasks/:id
Изменить задачу
Удалить задачу
DELETE
/tasks/:id
Удалить задачу

2. Реализация CRUD-операций

Получить список всех задач (GET /tasks)


app.get('/tasks', (req, res) => {
  res.json(tasks);
});

Это очень просто: сервер возвращает весь массив задач в формате JSON.

Получить задачу по id (GET /tasks/:id)


app.get('/tasks/:id', (req, res) => {
  const id = Number(req.params.id); // id приходит в виде строки!
  const task = tasks.find(t => t.id === id);

  if (!task) {
    // Если задачи с таким id нет — возвращаем 404
    return res.status(404).json({ error: 'Задача не найдена' });
  }

  res.json(task);
});

Обратите внимание: всегда проверяйте, что задача найдена. Иначе клиент получит "undefined" и будет грустить.

Добавить новую задачу (POST /tasks)


app.post('/tasks', (req, res) => {
  const { title } = req.body;

  if (!title || typeof title !== 'string') {
    // Проверяем, что title есть и это строка
    return res.status(400).json({ error: 'Некорректный заголовок задачи' });
  }

  // Генерируем новый id (берём максимальный из существующих и +1)
  const newId = tasks.length > 0 ? Math.max(...tasks.map(t => t.id)) + 1 : 1;

  const newTask = {
    id: newId,
    title,
    completed: false
  };

  tasks.push(newTask);

  res.status(201).json(newTask); // 201 — Created
});

Здесь мы принимаем данные из тела запроса. Не забываем про валидацию — иначе можно получить задачи с пустым названием или вообще без него.

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


app.put('/tasks/:id', (req, res) => {
  const id = Number(req.params.id);
  const { title, completed } = req.body;

  const task = tasks.find(t => t.id === id);

  if (!task) {
    return res.status(404).json({ error: 'Задача не найдена' });
  }

  // Обновляем только те поля, которые пришли
  if (title !== undefined) {
    if (typeof title !== 'string') {
      return res.status(400).json({ error: 'Некорректный заголовок задачи' });
    }
    task.title = title;
  }
  if (completed !== undefined) {
    if (typeof completed !== 'boolean') {
      return res.status(400).json({ error: 'Некорректный completed (ожидается true/false)' });
    }
    task.completed = completed;
  }

  res.json(task);
});

PUT обычно означает "заменить всю задачу", но на практике часто разрешают частичное обновление (иначе обновлять только статус задачи было бы неудобно). Можно реализовать и метод PATCH, но для простоты пока используем PUT.

Удалить задачу (DELETE /tasks/:id)


app.delete('/tasks/:id', (req, res) => {
  const id = Number(req.params.id);
  const index = tasks.findIndex(t => t.id === id);

  if (index === -1) {
    return res.status(404).json({ error: 'Задача не найдена' });
  }

  // Удаляем задачу из массива
  const deleted = tasks.splice(index, 1)[0];

  res.json(deleted);
});

Метод DELETE удаляет задачу и возвращает удалённый объект (или можно возвращать 204 No Content, если не хотите ничего отправлять в ответе).

3. Итоговый код приложения

Давайте объединим всё, что написали выше, в одно приложение. Это поможет не только закрепить материал, но и позволит вам скопировать и сразу попробовать код у себя.


const express = require('express');
const app = express();
const PORT = 3000;

// Middleware для чтения JSON из тела запроса
app.use(express.json());

// "База данных" — массив задач
let tasks = [
  { id: 1, title: "Купить хлеб", completed: false },
  { id: 2, title: "Сделать домашку по JS", completed: false },
  { id: 3, title: "Позвонить бабушке", completed: true }
];

// Получить все задачи
app.get('/tasks', (req, res) => {
  res.json(tasks);
});

// Получить одну задачу по id
app.get('/tasks/:id', (req, res) => {
  const id = Number(req.params.id);
  const task = tasks.find(t => t.id === id);
  if (!task) {
    return res.status(404).json({ error: 'Задача не найдена' });
  }
  res.json(task);
});

// Добавить новую задачу
app.post('/tasks', (req, res) => {
  const { title } = req.body;
  if (!title || typeof title !== 'string') {
    return res.status(400).json({ error: 'Некорректный заголовок задачи' });
  }
  const newId = tasks.length > 0 ? Math.max(...tasks.map(t => t.id)) + 1 : 1;
  const newTask = { id: newId, title, completed: false };
  tasks.push(newTask);
  res.status(201).json(newTask);
});

// Обновить задачу
app.put('/tasks/:id', (req, res) => {
  const id = Number(req.params.id);
  const { title, completed } = req.body;
  const task = tasks.find(t => t.id === id);
  if (!task) {
    return res.status(404).json({ error: 'Задача не найдена' });
  }
  if (title !== undefined) {
    if (typeof title !== 'string') {
      return res.status(400).json({ error: 'Некорректный заголовок задачи' });
    }
    task.title = title;
  }
  if (completed !== undefined) {
    if (typeof completed !== 'boolean') {
      return res.status(400).json({ error: 'Некорректный completed (ожидается true/false)' });
    }
    task.completed = completed;
  }
  res.json(task);
});

// Удалить задачу
app.delete('/tasks/:id', (req, res) => {
  const id = Number(req.params.id);
  const index = tasks.findIndex(t => t.id === id);
  if (index === -1) {
    return res.status(404).json({ error: 'Задача не найдена' });
  }
  const deleted = tasks.splice(index, 1)[0];
  res.json(deleted);
});

// Запуск сервера
app.listen(PORT, () => {
  console.log(`Сервер запущен на http://localhost:${PORT}`);
});

4. Как тестировать API? (Postman, curl, VS Code REST Client)

Чтобы проверить работу вашего API, можно использовать:

  • Postman — удобный инструмент с графическим интерфейсом.
  • curl — команда для терминала.
  • Встроенные плагины для VS Code, например, REST Client.

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

Получить все задачи:

GET http://localhost:3000/tasks

Получить одну задачу:

GET http://localhost:3000/tasks/2

Добавить задачу:

POST http://localhost:3000/tasks
Content-Type: application/json

{
  "title": "Погулять с собакой"
}

Обновить задачу:

PUT http://localhost:3000/tasks/1
Content-Type: application/json

{
  "completed": true
}

Удалить задачу:

DELETE http://localhost:3000/tasks/3

5. Типичные ошибки при создании REST API

Ошибка 1: Не проверяется существование задачи перед обновлением или удалением.
Если попытаться удалить несуществующую задачу, сервер должен вернуть 404, а не просто молча ничего не делать. Иначе клиент будет думать, что всё прошло успешно, а задача не исчезла — и будет негодовать.

Ошибка 2: Не проводится валидация данных.
Если не проверять, что title — строка, а completedboolean, в массив задач может попасть что угодно: числа, объекты, пустые строки, и даже котики (но котики — это хорошо, а вот мусорные данные — плохо).

Ошибка 3: Использование одинаковых id для разных задач.
Если при добавлении новой задачи не генерировать уникальный id, можно случайно затереть старую задачу. Генерируйте id на основе максимального текущего.

Ошибка 4: Неправильный Content-Type.
Если клиент отправляет данные без заголовка Content-Type: application/json, req.body может быть пустым. Не забывайте про этот заголовок при отправке запросов.

Ошибка 5: Необработанные ошибки приводят к падению сервера.
Если где-то возникла ошибка (например, опечатка в названии поля), сервер может упасть. Лучше всегда возвращать понятные сообщения об ошибке и не допускать аварийного завершения приложения.

Ошибка 6: Нарушение принципов REST.
Например, использовать GET для удаления или изменения данных — так делать нельзя, иначе REST API превращается в REST in peace.

Ошибка 7: Отсутствие статусов ответа.
Не забывайте возвращать правильные HTTP-статусы (201 для создания, 404 для не найдено, 400 для некорректных данных и т.д.). Это важно для корректной работы клиента.

1
Задача
Модуль 4: Node.js, Next.js и Angular, 5 уровень, 9 лекция
Недоступна
Получение списка задач
Получение списка задач
1
Задача
Модуль 4: Node.js, Next.js и Angular, 5 уровень, 9 лекция
Недоступна
Обновление существующей задачи по id
Обновление существующей задачи по id
3
Опрос
Парсинг тела запроса, 5 уровень, 9 лекция
Недоступен
Парсинг тела запроса
Парсинг тела запроса
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ