Docs›Arquitetura›MadWire vs REST
Arquitetura

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.

Alternativa fina: MadRecordService

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:

AliasClassePara quê
mad.authMadAuthenticateSessão do admin. É o middleware de POST /app/_mad-wire e de toda tela registrada por MadRoutes.
mad.permissionMadProgramPermissionPermissão por programa IAM na navegação.
mad.apiApp\Http\Middleware\MadApiTenantMiddlewareAPI pública stateless: autentica por token Bearer e resolve o escopo tenant/unit num passo só.
mad.api aponta para uma classe de APP, não do pacote

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

Tela do admin reativa MadWire
Listagem com filtros, formulário com validação, dashboard com drill-down — qualquer UI que o próprio MAD renderiza.
App mobile / terceiro REST
Qualquer cliente que não seja o browser carregando uma tela do MAD: app nativo, integração, webhook.
Atualização parcial de DOM MadWire
Trocar HTML de um seletor, abrir modal, mostrar toast — tudo isso é op do MadResponse, não JSON de domínio.
CRUD de domínio puro REST
Recurso que precisa existir como contrato JSON estável e versionável, independente de qualquer tela.
Evite: REST como motor de tela
Construir uma SPA própria que consome a REST API pra renderizar uma tela do admin reimplementa o que o MadComponent já faz de graça.
Evite: MadWire como API pública
mad_state é um detalhe de implementação do componente — não é um contrato estável para um cliente externo consumir.

Próximos passos