Visão geral
ApiResourceController sobre Eloquent com mestre-detalhe, exposto por mad.api (routes/api.php, Bearer stateless) ou mad.auth (grupo web) — vs o Driver REST assinado.
"REST API" aqui não é um processo separado nem um entry point próprio — é um conjunto de
rotas dentro do mesmo app Laravel, declaradas com
Route::apiResource e atendidas por controllers que estendem
Mad\Rest\ApiResourceController. Mesmo router, mesmo processo — só que a
resposta é sempre JSON, nunca HTML/MadWire.
Existem três superfícies sob o nome "REST" neste framework. Esta seção cobre as duas primeiras (mesmo controller, middlewares diferentes); a terceira é citada para você não confundir com elas.
| Superfície | Para quem | Autenticação | Onde |
|---|---|---|---|
ApiResourceController + mad.api (esta seção) |
Integração externa: outro sistema, script, job, app mobile — sem login web. | Authorization: Bearer mad_api_… — stateless, sem cookie nem CSRF,
com escopo de tenant/unit vindo do próprio token e abilities por rota. |
routes/api.php, prefixo /api, grupo api. |
ApiResourceController + mad.auth (esta seção) |
Seu próprio frontend (AJAX do admin) consumindo o banco do app via JSON. | mad.auth — sessão do admin + CSRF, igual a qualquer tela. |
routes/modules/*.php, grupo web com prefixo api. |
Driver REST (Mad\Rest\RestDriver*) |
Ferramenta externa (MadBuilder Database Manager) operando o banco do app sem VPN nem porta aberta. | HMAC assinado por chave pareada — sem sessão. | _mad/rest-driver/*, desabilitado por padrão. |
Veja Autenticação para o detalhe completo das três — inclusive por que o Driver REST não serve para expor dados ao seu frontend (ele devolve linhas cruas de SQL, não um recurso modelado).
// routes/api.php — API pública stateless (prefixo /api aplicado pelo bootstrap)
Route::middleware('mad.api:orders.read')->get('/orders', [OrderApiController::class, 'index']);
Route::middleware('mad.api')->apiResource('customers', CustomerApiController::class);
curl 'https://app.exemplo.com/api/orders' -H 'Authorization: Bearer mad_api_3f9c…'
O token carrega o escopo: cada mad_api_token é amarrado a um par
user + unit, e a unit determina tenant e — em multi-database — o banco. Emissão
pela tela admin Tokens de API (/app/tokens-api) ou por
php artisan mad:api-token; detalhes em
Tokens de API e abilities.
O que ApiResourceController resolve
É uma classe base abstrata: a subclasse só declara configuração
($model, filtros permitidos, eager-load, quais relações são "detalhe") — as
cinco ações REST (index/show/store/update/destroy)
já vêm prontas, casando 1:1 com Route::apiResource:
// app/Http/Controllers/Sys/ImportTemplateApiController.php — exemplo real deste repo
namespace App\Http\Controllers\Sys;
use App\Models\Sys\ImportTemplate;
use Mad\Rest\ApiResourceController;
class ImportTemplateApiController extends ApiResourceController
{
protected string $model = ImportTemplate::class;
protected array $searchable = ['title', 'code', 'database_name', 'active', 'created_by'];
protected array $sortable = ['title', 'code', 'created_at', 'updated_at'];
protected array $with = ['creator']; // eager-load default (belongsTo)
protected array $withCount = ['groups', 'users', 'logs'];
protected array $details = ['groups', 'users']; // hasMany tratadas como mestre-detalhe
protected ?string $defaultOrder = 'title asc';
}
// routes/modules/admin.php
Route::middleware('mad.auth')
->prefix('api')
->group(function () {
Route::apiResource('import-templates', \App\Http\Controllers\Sys\ImportTemplateApiController::class);
});
Isso já registra as cinco rotas (confira com php artisan route:list --path=api neste app):
GET|HEAD api/import-templates import-templates.index
POST api/import-templates import-templates.store
GET|HEAD api/import-templates/{import_template} import-templates.show
PUT|PATCH api/import-templates/{import_template} import-templates.update
DELETE api/import-templates/{import_template} import-templates.destroy
Ganhos sobre escrever cada endpoint à mão: mestre-detalhe com múltiplas relações
(várias hasMany no mesmo payload, cada uma sincronizada — cria/atualiza/apaga por
diff de PK), eager-load sem N+1 ($with/$withCount
aplicados automaticamente em toda query), validação por convenção
(Model::rules($id), a mesma usada pelo MadForm do admin), filtros via DSL
JSON e paginação com meta pronta.
Quando NÃO é a ferramenta certa
Para um endpoint AJAX raso de um único Active Record (sem detalhe, sem paginação-meta), a
alternativa mais simples é Mad\Service\MadRecordService — um serviço CRUD fino
despachado por verbo HTTP. Regra prática: se o recurso tem detalhe/relação no payload ou
precisa parecer uma API REST de verdade (status codes, paginação, projeção), use
ApiResourceController; se é só ler/gravar um registro plano, MadRecordService
resolve com menos código.
bootstrap/app.php configura
$exceptions->shouldRenderJsonWhen(fn ($request) => $request->is('api/*')).
Qualquer exceção que escape de um controller sob /api/* — incluindo
404 de rota inexistente — é renderizada como JSON pelo handler nativo do Laravel,
nunca como página HTML de erro.
Próximos passos
- Declarando rotas —
Route::apiResource, as propriedades de configuração e o que cada uma faz. - Autenticação —
mad.api(Bearer stateless),mad.auth+ CSRF, HMAC no Driver REST. - Tokens de API e abilities — emitir/revogar tokens e restringir por rota.
- JSON responses — formato de cada ação, status codes, projeção de campos.
- Exemplos práticos — CRUD completo mestre-detalhe, do model ao curl.