Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Тело запроса и JSON API

JSON API — сервер, который принимает и отдаёт данные в формате JSON, а не HTML-страницы. С ним работают фронтенд на React или Vue и мобильные приложения. Чтобы принять новую статью, сервер должен прочитать тело запроса (body) — данные, которые клиент отправил после заголовков.

Чтение тела

Объект req — поток: тело приходит частями. Их собирают циклом for await и склеивают. Создайте файл src/body.js:

// src/body.js
export async function readJson(req) {
  const chunks = [];
  for await (const chunk of req) {
    chunks.push(chunk);
  }
  const text = Buffer.concat(chunks).toString('utf8');
  return text === '' ? {} : JSON.parse(text);
}

Buffer.concat() объединяет байты в один кусок, toString('utf8') превращает их в текст. Пустое тело даёт пустой объект.

Маршрут POST /posts

Добавьте в src/server.js ветку для создания статьи. Функции addPost из src/posts.js и sendJson из урока про маршруты уже есть:

// src/server.js
import { readJson } from './body.js';
import { addPost } from './posts.js';
 
// внутри обработчика запроса:
if (req.method === 'POST' && pathname === '/posts') {
  const data = await readJson(req);
  const post = await addPost(data.title, data.body);
  return sendJson(res, 201, post);
}

Код 201 Created сообщает клиенту, что запись создана. В ответ отправляют саму статью — с присвоенным номером и датой.

Проверка запросом

Браузер отправляет POST только из формы, поэтому API удобно проверять программой curl в терминале:

curl -X POST http://localhost:3000/posts \
  -H "Content-Type: application/json" \
  -d '{"title": "Новая статья", "body": "Текст статьи"}'

В ответ придёт объект вида {"id":3,"title":"Новая статья",...}, а http://localhost:3000/posts покажет статью в списке.

Проверка данных

Клиент может прислать что угодно. Перед сохранением данные проверяют и при ошибке отвечают 400:

// src/server.js
if (req.method === 'POST' && pathname === '/posts') {
  let data;
  try {
    data = await readJson(req);
  } catch {
    return sendJson(res, 400, { error: 'Тело запроса — не JSON' });
  }
 
  if (typeof data.title !== 'string' || data.title.trim() === '') {
    return sendJson(res, 400, { error: 'Нужен заголовок' });
  }
 
  const post = await addPost(data.title.trim(), String(data.body ?? ''));
  return sendJson(res, 201, post);
}

JSON.parse() бросает ошибку на неправильном тексте — её перехватываем и сообщаем клиенту. curl без поля title получит {"error":"Нужен заголовок"}.

Комментарии

Комментарии к статье принимают так же, по адресу /posts/1/comments. Номер статьи достают регулярным выражением /^\/posts\/(\d+)\/comments$/, проверяют, что статья существует, и сохраняют комментарий в её массив comments. Попробуйте написать эту ветку сами по образцу.

Ошибки сервера

Если внутри обработчика случится непредвиденная ошибка, сервер должен ответить 500, а не оставить запрос висеть. Оберните тело обработчика в try...catch:

// src/server.js
const server = http.createServer(async (req, res) => {
  try {
    await handle(req, res);
  } catch (error) {
    console.error(error);
    sendJson(res, 500, { error: 'Ошибка сервера' });
  }
});

Здесь handle — функция со всеми маршрутами из этого и прошлого уроков.

Частая ошибка

Без заголовка Content-Type: application/json в ответе многие клиенты получат данные как простой текст и не разберут их. Отправляйте JSON только через одну функцию — sendJson(), — тогда заголовок не забудется ни в одном маршруте.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.