Тело запроса и 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(), — тогда заголовок не забудется ни в одном маршруте.