Версионирование API для веб-приложения
Версионирование API — способ вводить breaking changes, не ломая существующих клиентов. Без версионирования любое изменение схемы ответа, удаление поля или переименование эндпоинта сломает мобильные приложения, партнёрские интеграции и скрипты, которые вы не контролируете.
Стратегии версионирования
URL-версионирование — самый явный способ:
GET /api/v1/articles
GET /api/v2/articles
Плюс: очевидно из URL, легко кэшировать на CDN, просто для разработчика. Минус: URL «загрязняется» версией, ресурс /articles дублируется.
Header-версионирование:
GET /api/articles
Accept: application/vnd.myapp.v2+json
Чище с REST-перспективы, но сложнее тестировать (curl требует явного заголовка), плохо кэшируется без Vary: Accept.
Query parameter:
GET /api/articles?version=2
Только для крайних случаев — смешивает версию с бизнес-параметрами запроса.
Рекомендация: URL-версионирование для большинства проектов. Header — если API уже в production и URL менять нельзя.
Реализация в Laravel
// routes/api.php
Route::prefix('v1')->group(base_path('routes/api_v1.php'));
Route::prefix('v2')->group(base_path('routes/api_v2.php'));
// routes/api_v2.php
Route::apiResource('articles', App\Http\Controllers\V2\ArticleController::class);
Контроллеры V2 наследуют от V1, переопределяя только изменившиеся методы:
namespace App\Http\Controllers\V2;
use App\Http\Controllers\V1\ArticleController as V1Controller;
class ArticleController extends V1Controller
{
public function index(Request $request)
{
// V2: добавили поле excerpt, убрали body из списка
return ArticleV2Resource::collection(
Article::paginate($request->per_page ?? 20)
);
}
}
Реализация в NestJS
// main.ts
app.setGlobalPrefix('api');
// modules/v1/v1.module.ts и v2/v2.module.ts
@Controller('v1/articles')
export class ArticleV1Controller { ... }
@Controller('v2/articles')
export class ArticleV2Controller { ... }
Или через NestJS versioning API:
app.enableVersioning({ type: VersioningType.URI });
@Controller({ path: 'articles', version: '2' })
export class ArticleV2Controller {
@Get()
findAll() { ... }
}
Жизненный цикл версий
Типичный процесс:
- Новая версия объявляется в
Changelogсо списком breaking changes - Старая версия получает статус
deprecated— заголовокSunsetв ответах - Через 6–12 месяцев старая версия отключается
// Middleware добавляет Deprecation-заголовок к V1-ответам
class AddDeprecationHeader
{
public function handle($request, Closure $next)
{
$response = $next($request);
if (str_starts_with($request->path(), 'api/v1/')) {
$response->headers->set('Deprecation', 'true');
$response->headers->set('Sunset', 'Sat, 01 Jan 2026 00:00:00 GMT');
$response->headers->set('Link', '<https://api.example.com/v2/>; rel="successor-version"');
}
return $response;
}
}
Что является breaking change
Не все изменения требуют новой версии. Безопасные изменения (backward-compatible):
- Добавление нового поля в ответ
- Добавление нового необязательного параметра запроса
- Добавление нового эндпоинта
- Добавление нового значения в enum (если клиент игнорирует неизвестные значения)
Breaking changes, требующие новой версии:
- Удаление поля из ответа
- Переименование поля
- Изменение типа поля (
string→integer) - Изменение формата (
2024-01-15→1705276800) - Удаление эндпоинта
- Изменение семантики метода (например, PATCH стал вести себя как PUT)
API changelog
Версионирование без документации изменений бесполезно. Формат CHANGELOG.md для API:
## v2.0.0 (2025-03-01)
### Breaking Changes
- `GET /articles` — убрано поле `body`, добавлено `excerpt` (первые 200 символов)
- `POST /articles` — поле `tags` теперь массив ID, не строк
### New Features
- `GET /articles/{id}/related` — похожие статьи
- Cursor-based пагинация: параметр `after` вместо `page`
## v1.x — Deprecated
Поддерживается до 2026-01-01. Используйте v2.
Версионирование OpenAPI-спецификаций
Отдельный файл на версию:
docs/
openapi.v1.yaml
openapi.v2.yaml
Или через $ref между файлами — переиспользование общих схем (ErrorResponse, Pagination) без дублирования.
Сроки
Настройка URL-версионирования с разводкой роутов, наследованием контроллеров, Deprecation-заголовками: 2–3 дня. С автоматическим changelog, Sunset-мониторингом (алерт при превышении дедлайна версии) и раздельными OpenAPI-файлами: 1 неделя.







