MadWire vs REST
Quando usar o endpoint reativo do MadWire (/app/_mad-wire) vs a REST API (Route::apiResource → ApiResourceController).
Duas perguntas diferentes têm duas respostas diferentes neste framework: "como o browser atualiza um pedaço da tela sem recarregar a página?" é o MadWire; "como um sistema externo (mobile, integração, terceiro) lê e grava dados em JSON?" é a API REST. São mecanismos independentes, com bases de código, autenticação e contratos de payload diferentes — misturar os dois é o erro mais comum ao decidir onde colocar um novo endpoint.
Tabela de decisão
| MadWire reativo | REST API | |
|---|---|---|
| Endpoint | POST /app/_mad-wire (admin) ou POST /public/_mad-wire (páginas públicas) |
Rotas declaradas via Route::apiResource(...) ou Route::get/post(...) |
| Handler | Mad\Component\MadComponentHandler::process() |
Subclasse de Mad\Rest\ApiResourceController |
| Cliente | O próprio browser, via mad-livewire.js — nunca chamado manualmente |
Mobile app, integração, terceiro, ou qualquer client HTTP |
| Estado | mad_state criptografado (AES-256-GCM) — todo o estado do componente viaja no token |
Stateless — cada request é autossuficiente, sem token de estado |
| Payload | form-encoded (mad_state, mad_id, mad_action, mad_model[], mad_params) |
JSON in/out |
| Resposta | { id, html } ou { partial: true, ops: [...] } |
{ data, meta } (listagem) ou o recurso puro (show/store/update) |
| CSRF | Token nativo do Laravel (header X-CSRF-TOKEN) — o mad_state em si já é autenticado por GCM |
Depende da rota — tipicamente mad.auth (sessão) para consumo interno, ou mad.api (Bearer stateless) para consumo externo |
MadWire — /app/_mad-wire e /public/_mad-wire
As duas rotas convergem para a mesma implementação —
Mad\Component\MadComponentHandler::process($_POST) — só muda o controller que
a chama: MadAppController::wire() no admin (atrás de mad.auth) ou
MadSiteWireController::handle() em páginas públicas reativas
(MadSitePage, sem sessão admin). Você nunca escreve código que chama esse
endpoint diretamente — ele é disparado automaticamente pelo mad-livewire.js
sempre que um elemento com mad:click, mad:model ou
mad:submit dispara.
// app/components/admin/ProdutoListagem.php — MadComponent reativo
class ProdutoListagem extends MadDataGrid
{
protected static string $wrapper = self::INTERNAL;
protected string $model = Produto::class;
public function onFiltrar(MadRequest $req): void
{
$this->busca = $req->string('busca');
// auto-bind: o framework compara o estado antes/depois e gera as ops sozinho
}
protected function view(): string|array
{
return 'admin.produto-listagem';
}
}
// Nunca chamado direto pelo dev — o JS posta automaticamente em mad:click/mad:model:
// POST /app/_mad-wire { mad_state, mad_id, mad_action: 'onFiltrar', mad_model: {...} }
Detalhes do que acontece dentro do handler (decriptação do estado, diff de props, geração de ops parciais) ficam em MadWire por dentro.
REST API — Mad\Rest\ApiResourceController
A camada REST do framework (packages/mad-framework/src/mad/rest/) tem como
base abstrata o Mad\Rest\ApiResourceController: CRUD completo sobre um model
Eloquent, casando com Route::apiResource — a subclasse só declara
configuração (model, campos pesquisáveis/ordenáveis, eager-load, projeção), as ações
index/show/store/update/destroy
vêm prontas da base.
// app/control/api/ProdutoApiController.php — REST puro, sem MadComponent
use Mad\Rest\ApiResourceController;
class ProdutoApiController extends ApiResourceController
{
protected string $model = \App\Models\Produto::class;
protected array $searchable = ['nome', 'categoria_id'];
protected array $sortable = ['nome', 'preco'];
protected ?string $defaultOrder = 'nome asc';
}
// routes/web.php
Route::middleware('mad.auth')->prefix('api')->group(function () {
Route::apiResource('produtos', ProdutoApiController::class);
});
// GET /api/produtos?filters[categoria_id][eq]=5&sort=preco&per_page=20
// -> { "data": [...], "meta": { "total": 42, "per_page": 20, ... } }
A base já cobre mestre-detalhe (múltiplas relações hasMany
sincronizadas por diff de PK — cria/atualiza/apaga), um DSL de filtros
declarativo (eq, like, between, in,
is_null...), validação via Model::rules(), hooks
(authorize, beforeSave/afterSave,
beforeSaveDetail/afterSaveDetail, beforeDelete) e
transação atômica na conexão do model. A referência completa — incluindo o formato exato
de request/response, o algoritmo de sync de detalhes e os gotchas de concorrência — está em
packages/mad-framework/src/mad/rest/README.md.
Para um endpoint AJAX simples (sem mestre-detalhe, sem validação, sem
paginação-meta), o Mad\Service\MadRecordService é a opção mais leve —
despacha por verbo HTTP (load/store/delete/
loadAll/deleteAll/countAll) configurado por
constantes. ApiResourceController é a escolha "rica" quando o recurso
precisa de filtros, projeção e detalhes.
Os 3 aliases de middleware registrados pelo pacote
Mad\MadServiceProvider::bootRoutes() registra exatamente três aliases que você
usa em routes/web.php:
| Alias | Classe | Para quê |
|---|---|---|
mad.auth | MadAuthenticate | Sessão do admin. É o middleware de POST /app/_mad-wire e de toda tela registrada por MadRoutes. |
mad.permission | MadProgramPermission | Permissão por programa IAM na navegação. |
mad.api | App\Http\Middleware\MadApiTenantMiddleware | API pública stateless: autentica por token Bearer e resolve o escopo tenant/unit num passo só. |
Diferente dos outros dois, a classe por trás de mad.api vive em
App\Http\Middleware\ — mesma decisão dos models IAM que
BelongsToTenant referencia: o esquema de token e a tabela de escopo são
do app, não do framework. O provider registra o alias de qualquer forma, então usar
->middleware('mad.api') num projeto que não implementou essa classe
estoura só na hora em que a rota é atingida. Confira se ela existe antes de aplicar.
Não confundir com o REST Driver (HMAC)
Mad\Rest\RestDriverController não é a sua API
O mesmo diretório rest/ também contém um proxy de SQL interno
(RestDriverController, RestDriverRunner,
RestDriverSigner) usado pelo MadBuilder para introspecção e execução de
schema remoto, autenticado por HMAC (middleware RestDriverHmac,
payload assinado com uma chave do keystore). É uma ferramenta de tooling
interno do builder — não tem relação com endpoints de aplicação. Para expor dados
do seu app, use ApiResourceController.
Desde o 5.54.0, a introspecção de schema que esse driver serve
(SchemaIntrospector) não faz mais COUNT(*) por tabela nem
3 queries de catálogo por tabela: rowCount vem da estatística do engine
em 1 query (information_schema.tables.table_rows no MySQL,
pg_class.reltuples no PostgreSQL) e colunas/FKs/PKs/índices são
carregados em lote para o schema inteiro. O campo novo
rowCountApprox (bool) diz se o número é estimado — no SQLite ele vem
false quando não há sqlite_stat1 e o
COUNT(*) foi realmente executado.
Quando usar cada um
mad_state é um detalhe de implementação do componente — não é um contrato estável para um cliente externo consumir.