Версионирование
Часть раздела REST API путешествия по Node.js на Coddy. Урок 22 из 25.
Изменение структуры ответа нарушает работу всех клиентов, которые уже его используют. Версия в пути позволяет старым и новым клиентам работать одновременно: сегодня /api/v1/orders, а при изменении структуры — /api/v2/orders. Когда путь меняется, старый путь отвечает перенаправлением на новый:
GET /api/orders/5 → 308 Location: /api/v1/orders/5
POST /api/orders → 308 Location: /api/v1/ordersКоды 301 и 302 позволяют клиентам повторить POST-запрос как GET, из-за чего тело запроса теряется. 308 означает: «повторить там тот же запрос», включая метод и тело. Внутри подключённого middleware в req.url уже нет пути монтирования, а req.originalUrl сохраняет его целиком:
// в app.use('/api/orders', ...) для GET /api/orders/5?full=1
req.url // '/5?full=1'
req.originalUrl // '/api/orders/5?full=1'Задание
ЛегкоВаш API будет меняться так, как старые клиенты не ожидают, поэтому добавьте номер версии в адреса. Текущий API становится версией 1 по адресу /api/v1/tasks, а старые адреса перенаправляют клиентов туда.
- В
server.jsподключите маршрутизатор задач по адресу/api/v1/tasksвместо/api/tasks. - Также в
server.jsдобавьте middleware, подключённый по адресу/api/tasksи выполняющий только перенаправление. Он отвечает со статусом 308, перенаправляя на тот же адрес с префиксом/api/v1и сохраняя остальную часть пути и строку запроса. В подключённом middlewarereq.originalUrlсодержит полный старый адрес. Например:GET /api/tasks/2перенаправляется на/api/v1/tasks/2.POST /api/tasksперенаправляется на/api/v1/tasks.GET /api/tasks?done=false&sort=titleперенаправляется на/api/v1/tasks?done=false&sort=title.
Используйте 308, а не 301: после ответа 308 клиент повторяет POST как POST, включая тело запроса.
- В
routes/tasks.jsмаршрут POST задаётLocationсо значением нового адреса, например/api/v1/tasks/4для задачи 4.
Проверьте: GET /api/tasks/2 возвращает 308, а на вкладке Headers отображается Location: /api/v1/tasks/2. В строке запроса отображается само перенаправление; переход по нему не выполняется. Отправьте POST-запрос с телом { "title": "Order new chairs" } на /api/tasks: вы получите ответ 308 с Location: /api/v1/tasks, задача не будет создана. Отправьте POST-запрос с тем же телом на /api/v1/tasks: получите ответ 201 с Location: /api/v1/tasks/4. GET /api/v1/tasks/2 возвращает задачу 2.
Попробуйте сами
import express from 'express';
import { sendError } from './errors.js';
import taskRoutes from './routes/tasks.js';
const BOARD_ORIGIN = 'https://board.example.com';
const app = express();
// Позволяет страницам на BOARD_ORIGIN обращаться к этому API из браузера.
app.use((req, res, next) => {
res.set('Access-Control-Allow-Origin', BOARD_ORIGIN);
if (req.method === 'OPTIONS') {
res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
res.set('Access-Control-Allow-Headers', 'Content-Type');
return res.status(204).end();
}
next();
});
app.use(express.json());
app.use(express.static('public'));
app.use('/api/tasks', taskRoutes);
// Ошибки из маршрутов попадают сюда. Тело запроса с некорректным JSON — это
// ошибка клиента (400); всё остальное — наша ошибка (500).
app.use((err, req, res, next) => {
if (err.type === 'entity.parse.failed') {
return sendError(res, 400, 'invalid_json', 'The body is not valid JSON');
}
console.error(err);
sendError(res, 500, 'server_error', 'Something went wrong');
});
app.listen(3000, () => {
console.log('Task tracker API on port 3000');
});
Все уроки раздела REST API
1Ресурсы и глаголы
Ресурсы и URLСоздание с помощью POST4Браузеры, версии и конфигурация
Что такое originРазрешение другого originCORSСтраница, вызывающая APIПотренируйтесь самостоятельно: Песочница Express