# Models e CRUD > defineModel registra schema Mongoose, metadata, permissões e um router REST completo — listagem paginada, busca, leitura, criação, atualização, exclusão, import e export. Uma model é a unidade de domínio do backend. Declarada uma vez, ela gera o schema Mongoose, a metadata usada pelo frontend e as rotas REST do CRUD, já com autenticação, RBAC, escopo de dados e auditoria. ```javascript title="backend/src/models/Pedido.js" const { defineModel, fields } = require("@oondemand/oon-core-back"); module.exports = defineModel({ name: "Pedido", // PascalCase — nome no registry e no Mongoose singular: "pedido", basePath: "/pedidos", // rota do CRUD no backend (/api/pedidos no navegador) scope: "tenant", // legacy | system | tenant | user schema: { numero: fields.string({ required: true, label: "Número" }), fornecedor: fields.ref("Fornecedor", { required: true, label: "Fornecedor" }), status: fields.enum(["Rascunho", "Enviado", "Aprovado"], { default: "Rascunho", label: "Status" }), valor: fields.currency({ label: "Valor" }), entregaEm: fields.date({ label: "Entrega" }), }, crud: { enabled: true, permissions: { read: "pedidos.read", write: "pedidos.write" }, }, }); ``` > [!TIP] > `defineModel` devolve a entrada do registry com `definition` e `mongooseModel`. Exportá-la permite reutilizar a model em rotas próprias com os helpers de escopo. ## Opções de `defineModel` | Opção | Descrição | |---|---| | `name` | **Obrigatório.** Nome PascalCase. | | `schema` | **Obrigatório.** Campos com [`fields`](/docs/backend/campos) ou definições Mongoose cruas (viram `kind: "raw"` na metadata). | | `singular` | Nome singular (padrão: `name` em minúsculas). | | `basePath` | Caminho do router CRUD, iniciado por `/`. | | `scope` | Escopo de dados: `legacy` (padrão), `system`, `tenant` ou `user`. Veja [Escopo e accessContext](/docs/backend/escopo-e-contexto). | | `tenancy` | `{ tenantField?, userField? }` — nomes dos campos de isolamento (padrão `tenantId` e `usuarioId`). | | `crud.enabled` | `false` desliga o CRUD automático (padrão: ligado). | | `crud.permissions` | `{ read, write }` — permissões RBAC exigidas. Forma recomendada. | | `crud.roles` | `{ read: [], write: [] }` — compatibilidade legada por perfil. | | `crud.populateRefs` | `true` ou lista de campos `ref` — devolve as referências com rótulo na listagem e na leitura. Veja [Rótulos de referências](#rotulos-de-referencias). | | `options` | Opções do schema Mongoose (`timestamps: true` já é aplicado). | Campos `unique` em models `tenant`/`user` viram índices compostos com o campo de escopo, para que a unicidade seja por tenant. ## Rotas geradas Com `basePath: "/pedidos"`: | Método e rota | Guard | Uso | |---|---|---| | `GET /pedidos` | `read` | Lista paginada com busca, ordenação e filtros | | `GET /pedidos/export` | `read` | Exporta todos os registros do escopo | | `GET /pedidos/:id` | `read` | Leitura por ID | | `POST /pedidos` | `write` | Criação | | `POST /pedidos/import` | `write` | Importação em lote (`rows: []`) | | `PUT /pedidos/:id` | `write` | Atualização | | `PATCH /pedidos/:id` | `write` | Atualização parcial | | `DELETE /pedidos/:id` | `write` | Exclusão | Todas exigem sessão autenticada. Mutações bem-sucedidas são auditadas em `ControleAlteracao` com as ações `adicionado`, `alterado` e `excluido`. ### Parâmetros da listagem ```http GET /api/pedidos?pageIndex=0&pageSize=20&searchTerm=acme&sort=valor:desc&status=Aprovado ``` | Parâmetro | Padrão | Regra | |---|---|---| | `pageIndex` | `0` | Página a partir de zero | | `pageSize` | `20` | Entre 1 e 200 | | `searchTerm` | — | Busca case-insensitive em todos os campos `searchable` | | `sort` | `createdAt:desc` | `campo:asc` ou `campo:desc` | | demais | — | Filtros de igualdade por campo (vazios são ignorados) | Resposta: ```json { "results": [{ "_id": "…", "numero": "PC-001", "valor": 1200 }], "pagination": { "currentPage": 0, "itemsPerPage": 20, "totalItems": 1, "totalPages": 1 } } ``` O filtro de escopo (`tenantId`/`usuarioId`) é sempre mesclado pelo servidor — o cliente não consegue removê-lo. ## Rótulos de referências Por padrão, campos `fields.ref(...)` voltam do CRUD como `ObjectId`. Para grids e cards mostrarem o nome do registro relacionado sem consultas extras, ative `populateRefs`: ```javascript defineModel({ name: "Pedido", schema: { clienteId: fields.ref("Cliente", { required: true, label: "Cliente" }), fornecedorId: fields.ref("Fornecedor", { label: "Fornecedor" }), }, crud: { enabled: true, populateRefs: ["clienteId"], // ou true para todas as referências }, }); ``` - O CRUD devolve somente `_id` e campos usuais de identificação da model referenciada (nome, razão social, descrição, código, e-mail e os campos pesquisáveis). - O escopo de tenant/usuário da model referenciada é aplicado também na população. - Exportações mantêm os identificadores originais. ## Pipeline de mutação Em `POST`, `PUT`, `PATCH` e importação, o Core: 1. carrega o registro atual quando existem regras declarativas ou `defineValidation`; 2. remove ou bloqueia campos `readonly` e calculados; 3. consolida os campos atuais com as alterações recebidas; 4. recalcula fórmulas em ordem de dependência; 5. executa validações declarativas e de processo (transições, bloqueios por etapa); 6. executa a validação JavaScript registrada; 7. persiste somente as alterações permitidas e os valores calculados; 8. dispara triggers, bindings e auditoria. ## Metadata O backend publica a metadata consumida pelo frontend: | Endpoint | Conteúdo | |---|---| | `GET /core/metadata` | Serviço, models, coleções, documentos, esteiras, features e menus | | `GET /core/models` | Resumo de todas as models | | `GET /core/models/:name` | Campos, tipos, rótulos, obrigatoriedade, `searchable`, `readonly`, `computed` | Campos de escopo são internos: fazem parte do schema e dos índices, mas não aparecem na metadata nem nos formulários. ## Duas formas de declarar | | `defineModel` (JS) | `central.domain.json` | |---|---|---| | Campos e CRUD | ✅ | ✅ | | Fórmulas `computed` e validações declarativas | — | ✅ | | `scope` tenant/user | ✅ | — (single_tenant dedicado) | | Esteiras (`central.process.json`) | — | ✅ (models citadas precisam estar no domínio) | > [!WARNING] > Não declare a mesma model nos dois lugares. O registry interrompe o boot para impedir duas fontes de verdade. A migração pode ser feita model por model. ## Boas práticas - Campos explícitos, com tipos, obrigatoriedade e enums. - Preserve campos de status para esteiras. - Evite regras complexas no schema — use validações e o manifesto. - Não use o CRUD genérico para armazenar segredos; veja [Segredos](/docs/capabilities/segredos). - Rota customizada só quando a operação não couber em CRUD, ação declarativa ou processo. Fonte: https://sdk.oonapps.com/docs/backend/models-e-crud