Docs›REST API›Declarando rotas
REST API

Declarando rotas

routes/api.php + mad.api:<ability> vs routes/modules + mad.auth; Route::apiResource, propriedades do controller, only/except.

Declarar um recurso REST é sempre o mesmo par: uma classe que estende Mad\Rest\ApiResourceController com a configuração, e uma linha Route::apiResource() que liga a URL às cinco ações padrão. O que muda é onde você declara a rota, e isso decide a autenticação:

ArquivoGrupoMiddlewarePrefixo
routes/api.php api (só SubstituteBindings: sem sessão, sem CSRF) mad.api — Bearer mad_api_* /api automático (vem do withRouting(api: …) em bootstrap/app.php)
routes/modules/*.php web (sessão + CSRF) mad.auth ->prefix('api') explícito

O controller — só configuração, sem lógica de transporte

A subclasse declara propriedades protegidas; a base resolve query, paginação, validação, sync de detalhes e serialização. Tabela completa:

PropriedadeTipoDefaultFunção
$modelstring— (obrigatório)FQCN do model Eloquent.
$primaryKey?stringnullPK do recurso; null deriva de getKeyName().
$searchablearray[]Whitelist de colunas filtráveis (DSL de filtros).
$sortablearray[]Whitelist de colunas ordenáveis (sort).
$witharray[]Eager-load default. Os $details entram junto (sem N+1).
$withCountarray[]withCount default.
$appendsarray[]Accessors computados a incluir (Model::append).
$indexFieldsarray[]Projeção da listagem. Vazio = todos menos $hidden.
$showFieldsarray[]Projeção do show/respostas de write.
$hiddenarray[]Campos sempre removidos da resposta.
$defaultOrder?stringnullOrdem default "coluna asc|desc" quando não vem sort.
$perPageint15Itens por página default.
$maxPerPageint200Teto de per_page (defesa contra abuso).
$detailsarray[]Relações hasMany tratadas como detalhe. Múltiplas.
$transformersarray[]campo => fn($value, $model). Ver addTransformer().

O único requisito do model: declarar as relações hasMany usadas em $details e, opcionalmente, Model::rules($id = null): array para validação.

A rota pública — routes/api.php + mad.api

routes/api.php é registrado por withRouting(api: …) em bootstrap/app.php: tudo ali já ganha o prefixo /api e o grupo api do Laravel. A rota só precisa do mad.api:

// routes/api.php
use App\Http\Controllers\Sales\OrderApiController;

// Só auth + escopo de tenant (qualquer token válido passa):
Route::middleware('mad.api')->apiResource('orders', OrderApiController::class);

// Com abilities — o token precisa cobrir a permissão exigida por CADA rota:
Route::middleware('mad.api:orders.read')->get('/orders', [OrderApiController::class, 'index']);
Route::middleware('mad.api:orders.write')->post('/orders', [OrderApiController::class, 'store']);

// Vírgula = TODAS exigidas (E lógico, não OU):
Route::middleware('mad.api:orders.write,orders.approve')
    ->post('/orders/{order}/approve', [OrderApiController::class, 'approve']);
A ability declarada aqui é a fonte da checklist na tela

A tela admin de emissão de tokens (/app/tokens-api) monta a checklist de abilities varrendo a route table atrás de mad.api:<ability> — MadApiTokenService::declaredAbilities(). Nada é digitado à mão: se a ability não está declarada em nenhuma rota, ela não aparece para ser concedida. Escolha nomes estáveis (dominio.acao, segmentos a-z0-9_- separados por ponto) — eles viram contrato com quem já tem token emitido.

A rota interna — Route::apiResource atrás de mad.auth

// routes/modules/admin.php
Route::middleware('mad.auth')
    ->prefix('api')
    ->group(function () {
        Route::apiResource('import-templates', \App\Http\Controllers\Sys\ImportTemplateApiController::class);
    });

Route::apiResource é o helper nativo do Laravel — não tem nada de mágica MAD aqui. Ele gera as cinco rotas REST padrão para o nome dado, mapeando o verbo HTTP + path para o método correspondente do controller:

Verbo + URIMétodo chamadoNome da rota
GET /import-templatesindex()import-templates.index
POST /import-templatesstore()import-templates.store
GET /import-templates/{import_template}show()import-templates.show
PUT|PATCH /import-templates/{import_template}update()import-templates.update
DELETE /import-templates/{import_template}destroy()import-templates.destroy

Como é o Route::apiResource nativo, as opções nativas funcionam normalmente: only/except para expor um subconjunto das ações, names para customizar nomes de rota, múltiplos recursos no mesmo grupo de middleware/prefixo.

// só leitura — sem store/update/destroy
Route::apiResource('reports', ReportApiController::class)->only(['index', 'show']);

// vários recursos no mesmo grupo
Route::middleware('mad.auth')->prefix('api')->group(function () {
    Route::apiResource('import-templates', ImportTemplateApiController::class);
    Route::apiResource('orders', OrderApiController::class);
});
Não existem sub-rotas para os detalhes

$details = ['items', 'payments'] não gera GET /orders/{order}/items. Os detalhes só existem embutidos no payload do master (eager-loaded no show/ index, sincronizados no store/update). Se você precisa de um endpoint independente para a tabela de detalhe, declare outro ApiResourceController para ela.

Resolução do ID — não é route-model binding do Eloquent

O parâmetro {import_template} chega como string/int cru, não como instância do model resolvida por binding implícito. resolveId() pega o último parâmetro da rota corrente (independente do nome que apiResource gerou) e, fora de uma rota (ex.: chamada direta em teste), cai para a PK no corpo/query string. Isso significa que renomear o parâmetro da rota não quebra nada.

Múltiplos módulos, mesmo padrão

Cada arquivo em routes/modules/*.php é autocontido — declara seu próprio grupo de middleware, sem herdar nada implícito do arquivo que o carrega. Um módulo costuma misturar telas do admin (MadRoutes, atrás de mad.auth+mad.permission) com um bloco de API REST interna (atrás de só mad.auth — endpoints JSON não passam pelo gate de permissão de programa):

// routes/modules/admin.php
Route::middleware(['mad.auth', 'mad.permission'])->group(function () {
    MadRoutes::resource('users', 'UserList', 'UserForm');
    // ...telas do admin
});

// ─── API REST (Mad\Rest\ApiResourceController) ──────────────────────────────
Route::middleware('mad.auth')
    ->prefix('api')
    ->group(function () {
        Route::apiResource('import-templates', \App\Http\Controllers\Sys\ImportTemplateApiController::class);
    });

Próximos passos

  • Autenticação — mad.api (Bearer stateless + escopo de tenant) e mad.auth (sessão + CSRF), lado a lado.
  • Tokens de API e abilities — emitir o token que a rota mad.api:… vai exigir.
  • JSON responses — formato de cada resposta, projeção e status codes.
  • Exemplos práticos — o mesmo ImportTemplateApiController em curl, do zero ao mestre-detalhe.