Suas ideias em realidade digital!
>
Conforme uma API cresce, novas regras, campos, formatos de resposta e integrações começam a surgir. O problema é que alterar diretamente um endpoint que já está em uso pode quebrar sistemas antigos, aplicativos mobile, integrações externas e painéis administrativos que dependem do comportamento atual.
Para evitar esse problema, usamos versionamento de API. Em PHP puro, uma forma simples e eficiente de fazer isso é criar rotas como
/api/v1 e /api/v2, permitindo que a versão antiga continue funcionando enquanto a nova evolui.
Neste artigo, você vai aprender como criar versionamento de API em PHP, organizar arquivos por versão, manter compatibilidade e evoluir endpoints sem quebrar o sistema.
Versionamento de API é a prática de separar diferentes versões dos endpoints para permitir mudanças sem afetar quem ainda usa a versão antiga.
Exemplo:
/api/v1/usuarios
/api/v2/usuarios
A rota /api/v1/usuarios pode continuar retornando o formato antigo, enquanto /api/v2/usuarios
retorna uma estrutura nova, com novos campos, novas regras ou novo padrão de resposta.
Isso evita que você precise mudar tudo de uma vez e dá tempo para os consumidores da API migrarem com segurança.
Muitas APIs começam simples. Primeiro existe um endpoint de cadastro, depois login, listagem, atualização, upload, relatórios, filtros, permissões e integrações externas.
Em algum momento, pode surgir a necessidade de mudar algo que já está sendo usado. Sem versionamento, uma alteração pequena pode causar erro em vários lugares.
Alguns exemplos de mudanças que podem quebrar integrações:
O versionamento permite criar uma versão nova sem destruir a versão antiga.
Nem toda alteração precisa de uma nova versão. Pequenas correções internas podem ser feitas na mesma versão. O versionamento é mais necessário quando a mudança altera o contrato da API.
O contrato da API é o conjunto de regras esperadas por quem consome o endpoint: URL, método HTTP, parâmetros, corpo da requisição, resposta JSON, status HTTP e regras principais.
| Tipo de mudança | Precisa nova versão? | Exemplo |
|---|---|---|
| Adicionar campo opcional na resposta | Nem sempre | Adicionar telefone sem remover nada |
| Corrigir bug interno | Não | Ajustar cálculo sem mudar resposta |
| Remover campo da resposta | Sim | Remover nome_completo |
| Renomear campo | Sim | Trocar nome por name |
| Mudar formato de data | Sim | De 04/05/2026 para 2026-05-04 |
| Mudar autenticação | Sim | De token simples para JWT Bearer |
Existem diferentes formas de versionar uma API. As mais comuns são por URL, por cabeçalho e por parâmetro.
/api/v1/usuarios
/api/v2/usuarios
É o modelo mais simples de entender, testar e documentar. Também é muito usado em APIs públicas e sistemas internos.
Accept: application/vnd.suaapi.v1+json
Accept: application/vnd.suaapi.v2+json
Esse modelo deixa a URL mais limpa, mas pode ser menos intuitivo para quem está começando.
/api/usuarios?version=1
/api/usuarios?version=2
Funciona, mas geralmente é menos organizado que separar por URL.
Uma organização simples pode ser:
/api
/core
response.php
database.php
auth.php
/v1
/controllers
UsuarioController.php
routes.php
/v2
/controllers
UsuarioController.php
routes.php
index.php
Nessa estrutura, arquivos comuns ficam em /core, enquanto os controladores e rotas específicas ficam separados por versão.
Assim, você consegue manter comportamentos diferentes para v1 e v2 sem misturar tudo no mesmo arquivo.
Em muitos projetos PHP puro, o Apache usa .htaccess para redirecionar as chamadas da API para um arquivo central.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
Com isso, chamadas como /api/v1/usuarios e /api/v2/usuarios podem ser tratadas pelo seu roteador PHP.
No arquivo index.php, você pode capturar a URL acessada usando REQUEST_URI.
<?php
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = trim($uri, '/');
$partes = explode('/', $uri);
print_r($partes);
Para uma URL como:
/api/v1/usuarios
O array pode ficar assim:
[
0 => "api",
1 => "v1",
2 => "usuarios"
]
Agora vamos criar um roteador simples identificando versão e recurso.
<?php
header('Content-Type: application/json; charset=utf-8');
function jsonResponse(int $statusCode, array $data): void
{
http_response_code($statusCode);
echo json_encode($data, JSON_UNESCAPED_UNICODE);
exit;
}
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = trim($uri, '/');
$partes = explode('/', $uri);
// Exemplo esperado: /api/v1/usuarios
$versao = $partes[1] ?? null;
$recurso = $partes[2] ?? null;
if (!in_array($versao, ['v1', 'v2'], true)) {
jsonResponse(404, [
'success' => false,
'message' => 'Versão da API não encontrada.'
]);
}
if ($recurso === 'usuarios') {
if ($versao === 'v1') {
require __DIR__ . '/v1/routes.php';
exit;
}
if ($versao === 'v2') {
require __DIR__ . '/v2/routes.php';
exit;
}
}
jsonResponse(404, [
'success' => false,
'message' => 'Rota não encontrada.'
]);
Esse exemplo é simples, mas mostra a ideia principal: identificar a versão e carregar as rotas correspondentes.
Imagine que na versão 1 a API retorna usuários assim:
{
"success": true,
"data": [
{
"id": 1,
"nome": "João Silva",
"email": "joao@exemplo.com"
}
]
}
Esse formato já está sendo usado por um frontend antigo.
Se você mudar nome para name, esse frontend pode quebrar.
Na versão 2, você pode criar uma resposta mais completa:
{
"success": true,
"data": [
{
"id": 1,
"name": "João Silva",
"email": "joao@exemplo.com",
"status": "active",
"created_at": "2026-05-04T10:30:00-03:00"
}
],
"meta": {
"version": "v2"
}
}
Assim, quem usa v1 continua funcionando, enquanto novos sistemas podem consumir v2.
Um controlador simples para a versão 1 poderia ser:
<?php
class UsuarioControllerV1
{
public function listar(): void
{
jsonResponse(200, [
'success' => true,
'data' => [
[
'id' => 1,
'nome' => 'João Silva',
'email' => 'joao@exemplo.com'
]
]
]);
}
}
Já a versão 2 pode ter outro formato:
<?php
class UsuarioControllerV2
{
public function listar(): void
{
jsonResponse(200, [
'success' => true,
'data' => [
[
'id' => 1,
'name' => 'João Silva',
'email' => 'joao@exemplo.com',
'status' => 'active',
'created_at' => date('c')
]
],
'meta' => [
'version' => 'v2'
]
]);
}
}
O banco pode ser o mesmo, mas a forma como a API transforma e entrega os dados pode mudar entre versões.
Exemplo de /api/v1/routes.php:
<?php
require_once __DIR__ . '/controllers/UsuarioController.php';
$method = $_SERVER['REQUEST_METHOD'];
$controller = new UsuarioControllerV1();
if ($recurso === 'usuarios' && $method === 'GET') {
$controller->listar();
}
jsonResponse(404, [
'success' => false,
'message' => 'Rota não encontrada na v1.'
]);
Exemplo de /api/v2/routes.php:
<?php
require_once __DIR__ . '/controllers/UsuarioController.php';
$method = $_SERVER['REQUEST_METHOD'];
$controller = new UsuarioControllerV2();
if ($recurso === 'usuarios' && $method === 'GET') {
$controller->listar();
}
jsonResponse(404, [
'success' => false,
'message' => 'Rota não encontrada na v2.'
]);
Nem tudo precisa ser duplicado entre v1 e v2.
O ideal é reaproveitar serviços, conexão com banco, autenticação e validações comuns.
Exemplo de estrutura:
/api
/core
database.php
response.php
/services
UsuarioService.php
/v1
/controllers
UsuarioController.php
/v2
/controllers
UsuarioController.php
O UsuarioService pode buscar os dados no banco, enquanto cada controller decide como transformar a resposta para sua versão.
<?php
class UsuarioService
{
public function listar(): array
{
return [
[
'id' => 1,
'nome' => 'João Silva',
'email' => 'joao@exemplo.com',
'status' => 'ativo',
'created_at' => '2026-05-04 10:30:00'
]
];
}
}
A v1 pode retornar nome e a v2 pode transformar para name, usando a mesma fonte de dados.
Uma boa prática é criar métodos específicos para transformar a saída.
<?php
private function transformarUsuarioV1(array $usuario): array
{
return [
'id' => $usuario['id'],
'nome' => $usuario['nome'],
'email' => $usuario['email']
];
}
private function transformarUsuarioV2(array $usuario): array
{
return [
'id' => $usuario['id'],
'name' => $usuario['nome'],
'email' => $usuario['email'],
'status' => $usuario['status'] === 'ativo' ? 'active' : 'inactive',
'created_at' => date('c', strtotime($usuario['created_at']))
];
}
Esse tipo de transformação evita que a estrutura do banco dite diretamente o contrato público da API.
Se sua API usa Swagger ou OpenAPI, cada versão deve estar bem documentada. Você pode separar documentos por versão:
/api/docs/v1
/api/docs/v2
Ou indicar a versão no próprio arquivo OpenAPI:
info:
title: Minha API
version: 2.0.0
Isso ajuda outros desenvolvedores a entenderem quais endpoints pertencem a cada versão e quais mudanças foram feitas.
Quando uma versão antiga for sair de uso, você pode avisar os consumidores com antecedência.
Uma forma simples é adicionar informações na resposta:
{
"success": true,
"data": [],
"meta": {
"version": "v1",
"deprecated": true,
"message": "A versão v1 será descontinuada. Migre para /api/v2."
}
}
Também é possível usar headers:
Deprecation: true
Sunset: Wed, 31 Dec 2026 23:59:59 GMT
O mais importante é não desligar uma versão antiga sem aviso, principalmente se houver integrações externas usando a API.
Ao criar uma nova versão, não copie tudo sem necessidade. Primeiro identifique exatamente o que mudou.
Se a mudança for grande o suficiente para quebrar consumidores antigos, crie uma nova versão.
Se a v1 já está em produção, evite alterar o contrato dela. Crie a v2 para mudanças incompatíveis.
Versionar não significa duplicar tudo. Reaproveite serviços e deixe diferente apenas a camada de entrada ou saída.
Se a v2 muda campos ou formatos, isso precisa estar claro na documentação.
Quem consome a API precisa de tempo para migrar. Avise com antecedência.
Nem toda alteração exige v2. Adicionar um campo opcional, por exemplo, normalmente pode ser feito na mesma versão.
| Item | v1 | v2 |
|---|---|---|
| Campo de nome | nome |
name |
| Status | ativo |
active |
| Data | 2026-05-04 10:30:00 |
2026-05-04T10:30:00-03:00 |
| Metadados | Não possui | meta.version |
Versionar uma API PHP é uma prática importante para evoluir o sistema sem quebrar integrações existentes.
Quando você separa rotas como /api/v1 e /api/v2, consegue manter a compatibilidade com aplicações antigas
enquanto cria melhorias para novos consumidores.
O ideal é usar versionamento quando houver mudanças incompatíveis no contrato da API, como alteração de nomes de campos, remoção de dados, mudança no formato de resposta, autenticação ou regras importantes.
Mesmo em PHP puro, é possível criar uma estrutura organizada, com rotas por versão, controllers separados e serviços compartilhados. Dessa forma, sua API fica mais profissional, escalável e segura para crescer.
É a prática de separar versões diferentes da API, como /api/v1 e /api/v2, para evoluir endpoints
sem quebrar sistemas que usam a versão antiga.
Quando a mudança altera o contrato da API e pode quebrar quem já consome o endpoint, como renomear campos, remover dados ou mudar o formato da resposta.
Para projetos PHP puro, o versionamento pela URL, como /api/v1/usuarios, costuma ser o mais simples de implementar,
testar e documentar.
Não. O ideal é reaproveitar serviços, conexão com banco, autenticação e validações. A diferença deve ficar principalmente nos controllers, rotas e transformação das respostas.
Sim. As versões podem usar o mesmo banco. O que muda é a forma como os dados são recebidos, tratados e retornados pela API.
Você pode documentar a descontinuação, adicionar informações no campo meta da resposta ou usar headers como
Deprecation e Sunset.
É a prática de separar versões diferentes da API, como /api/v1 e /api/v2, para evoluir endpoints
sem quebrar sistemas que usam a versão antiga.
Quando a mudança altera o contrato da API e pode quebrar quem já consome o endpoint, como renomear campos, remover dados ou mudar o formato da resposta.
Para projetos PHP puro, o versionamento pela URL, como /api/v1/usuarios, costuma ser o mais simples de implementar,
testar e documentar.
Não. O ideal é reaproveitar serviços, conexão com banco, autenticação e validações. A diferença deve ficar principalmente nos controllers, rotas e transformação das respostas.
Sim. As versões podem usar o mesmo banco. O que muda é a forma como os dados são recebidos, tratados e retornados pela API.
Você pode documentar a descontinuação, adicionar informações no campo meta da resposta ou usar headers como
Deprecation e Sunset.