Introdução ao GeneXus
O que é GeneXus?
GeneXus é uma plataforma de desenvolvimento low-code que gera código a partir de modelos declarativos. Diferente de builders visuais que limitam o desenvolvedor, o GeneXus gera aplicações completas em Java, .NET ou Angular a partir de definições de alto nível.
Pensando como desenvolvedor
Se você está acostumado com o fluxo:
Definir schema SQL → Criar models/entities → Escrever controllers → Criar views → Deploy
No GeneXus o fluxo é:
Definir Transactions (modelo) → Configurar Rules → Gerar aplicação completa → Deploy
O GeneXus analisa seu modelo de dados e infere relacionamentos, normalização, interfaces CRUD e lógica de navegação automaticamente. Você intervém apenas onde precisa de comportamento customizado.
Por que um dev experiente deveria conhecer GeneXus?
- Produtividade: CRUDs completos com validações são gerados em minutos
- Manutenção: mudanças no modelo propagam automaticamente para toda a aplicação
- Multi-plataforma: o mesmo modelo gera backends Java/.NET e frontends web/mobile
- Integração: suporte nativo a APIs REST, SOAP, mensageria
Instalação do GeneXus 18
Na Interface
- Acesse o site oficial da GeneXus e baixe a versão Trial do GeneXus 18
- Execute o instalador — a instalação é padrão Windows (next, next, finish)
- Na primeira execução, o GeneXus pedirá a ativação da licença trial
- Após ativar, você verá a tela inicial (Start Page) com opções para criar ou abrir Knowledge Bases
Requisitos: Windows 10/11, mínimo 8GB RAM, SQL Server (LocalDB incluso) ou MySQL/PostgreSQL
Capítulo 2 — Knowledge Base e Modelo de Dados
O que é uma Knowledge Base?
Uma Knowledge Base (KB) é o equivalente a um "projeto" ou "workspace" no GeneXus. Ela contém todos os objetos da sua aplicação: modelos de dados, lógica de negócio, interfaces e configurações.
| Conceito tradicional | GeneXus |
|---|---|
| Projeto (VS Code / IntelliJ) | Knowledge Base |
| Arquivos de código | Objetos GeneXus |
package.json / .csproj |
Propriedades da KB |
Na Interface — Criando uma Knowledge Base
- File → New → Knowledge Base
- Informe o nome da KB (ex:
SistemaVendas) - Escolha o generator principal (Java, .NET, Angular)
- Escolha o banco de dados (SQL Server, MySQL, PostgreSQL)
- Clique em Create — o GeneXus criará a estrutura da KB
Atributos
Atributos no GeneXus são equivalentes a colunas em uma tabela SQL ou campos de uma interface TypeScript.
Exemplo lado a lado
TypeScript:
interface Cliente {
clienteId: number;
clienteNome: string;
clienteEmail: string;
}
SQL:
CREATE TABLE Cliente (
ClienteId INT PRIMARY KEY,
ClienteNome VARCHAR(100),
ClienteEmail VARCHAR(200)
);
GeneXus:
No GeneXus, você não escreve SQL nem interfaces. Os atributos são definidos dentro de uma Transaction (veremos no próximo capítulo). O GeneXus cria a tabela, os tipos e a interface automaticamente.
ClienteId Numeric(4) - Identificador (chave primária, marcado com *)
ClienteNome Character(100) - Nome do cliente
ClienteEmail Character(200) - Email do cliente
Diferença chave: No GeneXus, um atributo é uma entidade global. Se
ClienteIdaparece em outra Transaction, o GeneXus entende automaticamente que é uma referência (foreign key). Não é preciso declarar relações manualmente.
Domínios
Domínios no GeneXus são tipos reutilizáveis — equivalem a type aliases no TypeScript ou DOMAIN no SQL.
Exemplo lado a lado — Domínios
TypeScript:
type Email = string; // alias semântico
type Preco = number; // alias semântico
type Status = "Ativo" | "Inativo";
interface Cliente {
email: Email;
}
GeneXus — Domínio:
Domínio: Email → Character(200)
Domínio: Preco → Numeric(10.2)
Domínio: Status → Character(10), com valores enumerados: "Ativo", "Inativo"
Quando você cria um atributo, pode associá-lo a um domínio. Se o domínio mudar (ex: Email passa de 200 para 300 caracteres), todos os atributos que usam esse domínio são atualizados automaticamente.
Na Interface — Criando Domínios
- No KB Explorer (painel lateral esquerdo), clique com botão direito em Domains
- Selecione New Domain
- Defina o nome, tipo de dado e, opcionalmente, valores enumerados
- Salve com Ctrl+S
Na Interface — Criando Atributos
Os atributos são criados diretamente dentro das Transactions (próximo capítulo). Não existem isoladamente — sempre pertencem a um contexto.
Capítulo 3 — Transactions (Modelo de Dados)
O que são Transactions?
Transactions são o coração do GeneXus. Elas definem simultaneamente:
- A estrutura da tabela no banco de dados (como
CREATE TABLE) - A entidade/model da aplicação (como uma Entity do TypeORM ou um Model do Prisma)
- A interface CRUD gerada automaticamente (como um scaffold do Rails/NestJS)
Exemplo lado a lado — Transactions
SQL + TypeORM:
// Entity
@Entity()
class Cliente {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 100 })
nome: string;
@Column({ length: 200 })
email: string;
}
-- SQL gerado
CREATE TABLE Cliente (
Id INT PRIMARY KEY AUTO_INCREMENT,
Nome VARCHAR(100),
Email VARCHAR(200)
);
GeneXus — Transaction Cliente:
{ClienteId* Numeric(4) Autonumber}
ClienteNome Character(100)
ClienteEmail Character(200)
O * indica chave primária. Autonumber equivale ao AUTO_INCREMENT. Com essa definição, o GeneXus:
- Cria a tabela no banco
- Gera a tela de cadastro (inserir, editar, excluir)
- Gera validações de tipo e chave primária
Primary Key, Relacionamentos e Normalização
Primary Key
No GeneXus, o atributo marcado com * é a PK. Equivale a:
PRIMARY KEY (ClienteId)
Relacionamentos (Foreign Keys)
No GeneXus, você não declara foreign keys. O relacionamento é inferido pelo nome do atributo.
SQL tradicional:
CREATE TABLE Pedido (
PedidoId INT PRIMARY KEY,
ClienteId INT REFERENCES Cliente(ClienteId), -- FK explícita
PedidoData DATE
);
GeneXus — Transaction Pedido:
{PedidoId* Numeric(4) Autonumber}
ClienteId Numeric(4) -- GeneXus INFERE que é FK para Cliente
ClienteNome Character(100) -- Atributo inferido (somente leitura)
PedidoData Date
Como ClienteId já existe na Transaction Cliente, o GeneXus entende que é uma referência. Ele até traz ClienteNome como campo somente-leitura na tela de Pedido.
Normalização Automática
O GeneXus aplica normalização automaticamente. Se você colocar ClienteNome na Transaction Pedido, ele não cria uma coluna redundante na tabela Pedido do banco. Em vez disso, resolve por JOIN na hora de exibir.
Para devs SQL: Pense nisso como uma VIEW automática. Você define o que quer ver, e o GeneXus resolve como buscar.
Transactions com Níveis (Relações 1:N)
Transactions podem ter níveis, que representam relações mestre-detalhe (1:N).
SQL tradicional:
CREATE TABLE Pedido (
PedidoId INT PRIMARY KEY,
ClienteId INT REFERENCES Cliente(ClienteId),
PedidoData DATE
);
CREATE TABLE PedidoItem (
PedidoId INT REFERENCES Pedido(PedidoId),
PedidoItemId INT,
ProdutoId INT REFERENCES Produto(ProdutoId),
PedidoItemQtd INT,
PedidoItemPreco DECIMAL(10,2),
PRIMARY KEY (PedidoId, PedidoItemId)
);
GeneXus — Transaction Pedido com nível:
{PedidoId* Numeric(4) Autonumber}
ClienteId Numeric(4)
ClienteNome Character(100)
PedidoData Date
// Nível (linha detalhe)
{PedidoItemId* Numeric(4) Autonumber}
ProdutoId Numeric(4)
ProdutoNome Character(100)
PedidoItemQtd Numeric(4)
PedidoItemPreco Numeric(10.2)
O nível indentado dentro da Transaction cria automaticamente:
- A tabela
PedidoItemcom FK composta (PedidoId,PedidoItemId) - Uma grid de itens na tela de cadastro do Pedido
- A relação 1:N entre Pedido e PedidoItem
Na Interface — Criando Transactions
- No KB Explorer, clique com botão direito → New → Transaction
- Dê um nome (ex:
Cliente) - Na aba Structure, adicione os atributos:
- Clique na primeira linha e digite o nome do atributo (ex:
ClienteId) - Defina o tipo de dado na coluna Type (ex:
Numeric(4)) - Marque o checkbox
*para indicar chave primária - Para autonumber: clique com botão direito no atributo → Autonumber → True
- Clique na primeira linha e digite o nome do atributo (ex:
- Para criar níveis (detalhes 1:N):
- Posicione o cursor após o último atributo do nível principal
- Clique no botão Insert Level na toolbar (ou botão direito → Insert Level)
- Adicione os atributos do nível filho
- Salve com Ctrl+S
- Execute Build → Build All para que o GeneXus crie as tabelas no banco
Capítulo 4 — Rules e Validações
O que são Rules?
Rules são regras de negócio declarativas associadas a uma Transaction. Elas definem validações, valores padrão e comportamentos — sem precisar escrever lógica imperativa.
Comparação com o mundo TypeScript/SQL
| GeneXus Rule | Equivalente TS/SQL |
|---|---|
ClienteNome.Required() |
Zod: z.string().min(1) / SQL: NOT NULL |
Default(PedidoData, &Today) |
TypeORM: @Column({ default: () => 'NOW()' }) |
Error("Email inválido") if ClienteEmail.IsEmpty() |
Zod: .refine() / Express middleware de validação |
Noaccept(ClienteId) |
Campo readonly / disabled no form |
Msg("Salvo com sucesso") on AfterComplete |
Toast/notification após submit |
Rules Básicas
Required
Torna um campo obrigatório. Equivale a NOT NULL no SQL + validação no frontend.
Zod (TypeScript):
const clienteSchema = z.object({
clienteNome: z.string().min(1, "Nome é obrigatório"),
clienteEmail: z.string().email("Email inválido"),
});
GeneXus Rules:
ClienteNome.Required("Nome é obrigatório");
ClienteEmail.Required("Email é obrigatório");
Default
Define valor padrão para um atributo.
TypeORM:
@Column({ default: () => 'CURRENT_DATE' })
pedidoData: Date;
@Column({ default: 'Ativo' })
clienteStatus: string;
GeneXus Rules:
Default(PedidoData, &Today);
Default(ClienteStatus, "Ativo");
Error e Msg
Error impede o salvamento e exibe mensagem. Msg exibe aviso sem impedir.
Express/Zod:
// Validação customizada
if (pedidoItemQtd <= 0) {
throw new BadRequestException("Quantidade deve ser maior que zero");
}
GeneXus Rules:
Error("Quantidade deve ser maior que zero") if PedidoItemQtd <= 0;
Msg("Atenção: pedido com valor alto") if PedidoTotal > 10000;
Noaccept
Torna um campo somente-leitura na interface (o usuário não pode editar).
Noaccept(PedidoTotal); // campo calculado, não editável
Noaccept(ClienteId) on Update; // não permite alterar ID ao editar
Equivale a
disabledoureadonlyem um campo de formulário HTML/React.
Rules com Condições
Rules podem ser condicionais usando if e podem disparar em momentos específicos:
// Desconto só para clientes VIP
Default(PedidoDesconto, 10) if ClienteCategoria = "VIP";
// Validação condicional
Error("CEP obrigatório para entregas") if PedidoTipoEntrega = "Correios" and ClienteCEP.IsEmpty();
// Timing: on AfterInsert, on BeforeInsert, on AfterComplete, etc.
Msg("Pedido criado com sucesso!") on AfterComplete;
Na Interface — Escrevendo Rules
- Abra a Transaction desejada (ex:
Cliente) - Clique na aba Rules (na parte inferior da tela, ao lado de Structure, Events, etc.)
- Digite as rules diretamente no editor de texto
- Cada rule termina com
; - Salve com Ctrl+S
Dica: O editor de Rules tem autocomplete — pressione
Ctrl+Spacepara ver opções disponíveis.
Capítulo 5 — Procedures (Lógica de Backend)
O que são Procedures?
Procedures são o equivalente GeneXus a funções ou services no backend. São usados para lógica de negócio, cálculos, consultas e operações em lote — tudo que não é CRUD direto.
| Conceito TypeScript | GeneXus |
|---|---|
| Função / Service | Procedure |
| Parâmetros de função | Parm rule |
return valor |
Variável de saída no Parm |
| Query com Prisma/TypeORM | for each |
if/else |
if/else |
console.log() |
msg() |
Comandos Essenciais
for each — Consulta ao banco
O for each é o SELECT do GeneXus. Ele itera sobre registros de uma tabela, e o GeneXus resolve automaticamente a query SQL.
Prisma (TypeScript):
// Listar todos os clientes ativos
const clientes = await prisma.cliente.findMany({
where: { clienteStatus: "Ativo" },
orderBy: { clienteNome: "asc" },
});
for (const cliente of clientes) {
console.log(`${cliente.clienteNome} - ${cliente.clienteEmail}`);
}
GeneXus Procedure:
for each Cliente
where ClienteStatus = "Ativo"
order ClienteNome
msg(ClienteNome + " - " + ClienteEmail)
endfor
O que acontece por baixo: O GeneXus gera um
SELECT ClienteNome, ClienteEmail FROM Cliente WHERE ClienteStatus = 'Ativo' ORDER BY ClienteNome. Você nunca escreve SQL diretamente.
where — Filtros
for each Pedido
where PedidoData >= &DataInicio
where PedidoData <= &DataFim
where ClienteId = &ClienteId // join automático com Cliente
// GeneXus resolve o JOIN automaticamente
msg(ClienteNome + " - Pedido #" + PedidoId.ToString())
endfor
Equivalente SQL gerado:
SELECT C.ClienteNome, P.PedidoId
FROM Pedido P
JOIN Cliente C ON P.ClienteId = C.ClienteId
WHERE P.PedidoData >= @DataInicio
AND P.PedidoData <= @DataFim
AND P.ClienteId = @ClienteId
if/else
if &Quantidade > 100
&Desconto = 15
else
if &Quantidade > 50
&Desconto = 10
else
&Desconto = 0
endif
endif
new — Inserir registros
Prisma:
await prisma.logAuditoria.create({
data: {
logData: new Date(),
logDescricao: "Pedido criado",
logUsuario: usuario.id,
},
});
GeneXus:
new
LogData = &Today
LogDescricao = "Pedido criado"
LogUsuario = &UsuarioId
endnew
Parâmetros de Entrada e Saída
Procedures recebem e retornam dados via a rule Parm.
TypeScript:
function calcularTotalPedido(pedidoId: number): number {
// ... lógica
return total;
}
GeneXus — Procedure CalcularTotalPedido:
Rules:
Parm(in: &PedidoId, out: &Total);
Source:
&Total = 0
for each PedidoItem
where PedidoId = &PedidoId
&Total += PedidoItemQtd * PedidoItemPreco
endfor
in:= parâmetro de entrada (somente leitura)out:= parâmetro de saída (o "return")inout:= entrada e saída (passagem por referência)
Chamando um Procedure
// De outro Procedure ou evento
CalcularTotalPedido(PedidoId, &Total)
msg("Total do pedido: " + &Total.ToString())
Equivale a:
const total = calcularTotalPedido(pedidoId);
console.log(`Total do pedido: ${total}`);
Na Interface — Criando um Procedure
- No KB Explorer, clique com botão direito → New → Procedure
- Dê um nome (ex:
CalcularTotalPedido) - Na aba Rules, defina os parâmetros com
Parm(...) - Na aba Source, escreva a lógica (for each, if, new, etc.)
- Na aba Variables, defina as variáveis locais usadas (prefixadas com
&)- Clique em Insert para adicionar uma variável
- Defina nome, tipo de dado (pode ser baseado em um atributo existente)
- Salve com Ctrl+S
Variáveis vs Atributos: No GeneXus,
&Total(com&) é uma variável local.PedidoId(sem&) é um atributo do banco. A diferença é crucial: atributos navegam tabelas, variáveis são temporárias.
Capítulo 6 — Data Providers
O que são Data Providers?
Data Providers são objetos especializados em retornar dados estruturados — pense neles como funções que retornam JSON/objetos tipados. São ideais para alimentar APIs REST, grids e relatórios.
| Conceito TypeScript | GeneXus |
|---|---|
Função que retorna { data: [...] } |
Data Provider |
| Interface/Type do retorno | SDT (Structured Data Type) |
Array.map() sobre resultados |
Grupo com for each implícito |
Exemplo lado a lado
TypeScript:
interface ClienteDTO {
id: number;
nome: string;
email: string;
totalPedidos: number;
}
function listarClientesAtivos(): ClienteDTO[] {
return clientes
.filter(c => c.status === "Ativo")
.map(c => ({
id: c.id,
nome: c.nome,
email: c.email,
totalPedidos: c.pedidos.length,
}));
}
GeneXus — Data Provider DPClientesAtivos:
Primeiro, crie um SDT (tipo estruturado) para o retorno:
SDT: ClienteDTO (Collection)
ClienteDTOId Numeric(4)
ClienteDTONome Character(100)
ClienteDTOEmail Character(200)
ClienteDTOTotalPedidos Numeric(4)
Depois, o Data Provider:
// Output: ClienteDTO (Collection)
ClienteDTO
{
ClienteDTOId = ClienteId
ClienteDTONome = ClienteNome
ClienteDTOEmail = ClienteEmail
ClienteDTOTotalPedidos = count(PedidoId)
where ClienteStatus = "Ativo"
}
A grande diferença: No Data Provider, cada "grupo" (entre
{ }) gera automaticamente uma iteração sobre a tabela base. Não é preciso escreverfor each— o GeneXus infere a navegação a partir dos atributos usados.
Estrutura Hierárquica
Data Providers suportam retornos aninhados, como objetos JSON complexos.
TypeScript:
interface PedidoCompleto {
id: number;
data: Date;
cliente: { nome: string };
itens: Array<{
produto: string;
qtd: number;
preco: number;
}>;
}
GeneXus — Data Provider com níveis:
PedidoCompleto
{
PedidoId = PedidoId
PedidoData = PedidoData
ClienteNome = ClienteNome
Itens
{
ProdutoNome = ProdutoNome
PedidoItemQtd = PedidoItemQtd
PedidoItemPreco = PedidoItemPreco
}
}
O nível Itens aninhado gera automaticamente a relação 1:N, iterando sobre os itens de cada pedido.
Na Interface — Criando um Data Provider
- No KB Explorer, clique com botão direito → New → Data Provider
- Dê um nome (ex:
DPClientesAtivos) - Na aba Source, defina a estrutura hierárquica do retorno
- Na aba Rules, defina
Parm(out: &Resultado)onde&Resultadoé do tipo SDT definido - Para criar o SDT (tipo do retorno):
- New → Structured Data Type
- Defina os campos e seus tipos
- Marque como Collection se retornar uma lista
- Salve com Ctrl+S
Capítulo 7 — Business Components
O que são Business Components?
Business Components (BC) permitem manipular dados por código — inserir, atualizar e excluir registros programaticamente, mantendo todas as validações (Rules) da Transaction.
| Conceito TypeScript/ORM | GeneXus |
|---|---|
prisma.cliente.create({...}) |
&Cliente.Save() (insert) |
prisma.cliente.update({...}) |
&Cliente.Save() (update) |
prisma.cliente.delete({...}) |
&Cliente.Delete() |
prisma.cliente.findUnique({...}) |
&Cliente.Load(id) |
| Try/catch em operações | &Cliente.GetMessages() |
| Repository Pattern | Business Component |
Habilitando Business Components
Por padrão, Transactions não são Business Components. Você precisa habilitar a propriedade.
Exemplo lado a lado — Inserir
Prisma (TypeScript):
try {
const novoCliente = await prisma.cliente.create({
data: {
clienteNome: "João Silva",
clienteEmail: "joao@email.com",
clienteStatus: "Ativo",
},
});
console.log(`Cliente criado: ID ${novoCliente.id}`);
} catch (error) {
console.error("Erro ao criar cliente:", error.message);
}
GeneXus:
&Cliente = new()
&Cliente.ClienteNome = "João Silva"
&Cliente.ClienteEmail = "joao@email.com"
&Cliente.ClienteStatus = "Ativo"
&Cliente.Save()
if &Cliente.Success()
msg("Cliente criado: ID " + &Cliente.ClienteId.ToString())
else
// Iterar sobre erros (como validações de Rules)
for &Msg in &Cliente.GetMessages()
msg(&Msg.Description)
endfor
endif
Exemplo lado a lado — Atualizar
Prisma:
await prisma.cliente.update({
where: { id: clienteId },
data: { clienteEmail: "novo@email.com" },
});
GeneXus:
&Cliente.Load(ClienteId)
&Cliente.ClienteEmail = "novo@email.com"
&Cliente.Save()
Exemplo lado a lado — Excluir
Prisma:
await prisma.cliente.delete({
where: { id: clienteId },
});
GeneXus:
&Cliente.Load(ClienteId)
&Cliente.Delete()
Exemplo — Inserir Pedido com Itens (Mestre-Detalhe)
Este é um cenário comum: inserir um registro pai com filhos.
Prisma:
await prisma.pedido.create({
data: {
clienteId: 1,
pedidoData: new Date(),
itens: {
create: [
{ produtoId: 10, pedidoItemQtd: 2, pedidoItemPreco: 49.90 },
{ produtoId: 20, pedidoItemQtd: 1, pedidoItemPreco: 99.90 },
],
},
},
});
GeneXus:
&Pedido = new()
&Pedido.ClienteId = 1
&Pedido.PedidoData = &Today
// Adicionar item 1
&PedidoItem = new()
&PedidoItem.ProdutoId = 10
&PedidoItem.PedidoItemQtd = 2
&PedidoItem.PedidoItemPreco = 49.90
&Pedido.PedidoItem.Add(&PedidoItem)
// Adicionar item 2
&PedidoItem = new()
&PedidoItem.ProdutoId = 20
&PedidoItem.PedidoItemQtd = 1
&PedidoItem.PedidoItemPreco = 99.90
&Pedido.PedidoItem.Add(&PedidoItem)
&Pedido.Save()
if &Pedido.Success()
msg("Pedido criado com " + &Pedido.PedidoItem.Count.ToString() + " itens")
else
for &Msg in &Pedido.GetMessages()
msg(&Msg.Description)
endfor
endif
Tratamento de Erros
O método GetMessages() retorna todas as mensagens de erro geradas pelas Rules da Transaction. Isso é equivalente a validações que disparam exceptions no backend.
&Cliente.Save()
if not &Cliente.Success()
for &Msg in &Cliente.GetMessages()
// &Msg.Type = 1 (erro), 0 (warning)
if &Msg.Type = 1
msg("ERRO: " + &Msg.Description)
else
msg("AVISO: " + &Msg.Description)
endif
endfor
endif
Na Interface — Habilitando Business Components
- Abra a Transaction desejada (ex:
Cliente) - No painel Properties (geralmente à direita), procure a propriedade Business Component
- Mude de No para Yes
- Salve com Ctrl+S
- Agora você pode declarar variáveis do tipo
Clienteem Procedures e Web Panels:- Na aba Variables, crie uma variável com nome
Cliente - O tipo será automaticamente
Cliente(o Business Component)
- Na aba Variables, crie uma variável com nome
Importante: Quando BC está habilitado, toda operação via código (
Save,Delete) respeita as Rules definidas na Transaction. SeClienteNome.Required()existe como Rule, oSave()falhará se o nome estiver vazio — assim como validações de schema falham no backend.
Capítulo 8 — Web Panels (Interface)
O que são Web Panels?
Web Panels são o equivalente GeneXus a componentes/páginas no mundo web. Se você vem do React, pense em Web Panels como componentes de página com layout visual, estado local (variáveis) e event handlers.
| Conceito React/HTML | GeneXus |
|---|---|
| Componente de página | Web Panel |
| JSX / template HTML | Layout visual (designer) |
useState / variáveis locais |
Variáveis & |
onClick, onSubmit |
Event 'NomeBotao' |
useEffect(() => {}, []) |
Event Start |
| Re-render / fetch de dados | Event Refresh |
<table> / data grid |
Grid |
<form> |
Tabela com controles |
Layout: Tabelas, Grids e Controles
O layout de uma Web Panel é construído visualmente no designer (drag and drop), mas os conceitos mapeiam diretamente para HTML:
- Table →
<div>ou<table>para organizar layout - Grid →
<table>com dados dinâmicos (como uma DataTable) - TextBlock →
<span>ou<label> - Attribute/Variable →
<input>vinculado a um dado - Button →
<button>com evento associado - Image →
<img>
Eventos
Eventos em Web Panels funcionam como event handlers em JavaScript.
React:
const [filtro, setFiltro] = useState("");
// Equivale a Event Start
useEffect(() => {
carregarDados();
}, []);
// Equivale a Event 'Buscar'
const handleBuscar = () => {
const resultados = dados.filter(d => d.nome.includes(filtro));
setDadosFiltrados(resultados);
};
// Equivale a Event Refresh
const carregarDados = async () => {
const res = await fetch("/api/clientes");
setDados(await res.json());
};
GeneXus Web Panel:
// Executa ao carregar a página
Event Start
&Filtro = ""
EndEvent
// Executa quando o grid precisa carregar/recarregar dados
Event Refresh
// O grid carrega automaticamente baseado nos atributos definidos
// Filtros são aplicados via Conditions
EndEvent
// Executa ao clicar no botão "Buscar"
Event 'Buscar'
// Recarrega o grid com o novo filtro
Grid1.Search()
EndEvent
// Executa ao clicar em uma linha do grid
Event Grid1.Click
// Navegar para detalhe
ClienteDetalhe(ClienteId)
EndEvent
Variáveis Locais
Variáveis em Web Panels (prefixadas com &) são como variáveis de estado do componente.
// Variáveis definidas na aba Variables:
// &FiltroNome Character(100)
// &DataInicio Date
// &TotalExibido Numeric(10.2)
Event 'Calcular'
&TotalExibido = 0
for each Pedido
where PedidoData >= &DataInicio
where ClienteNome like &FiltroNome + "%" // LIKE no SQL
&TotalExibido += PedidoTotal
endfor
EndEvent
Grids — Listagem de Dados
Um Grid em Web Panel é como uma tabela HTML alimentada por dados do banco.
React com tabela:
<table>
<thead>
<tr><th>Nome</th><th>Email</th><th>Status</th></tr>
</thead>
<tbody>
{clientes.map(c => (
<tr key={c.id}>
<td>{c.nome}</td>
<td>{c.email}</td>
<td>{c.status}</td>
</tr>
))}
</tbody>
</table>
GeneXus: No designer visual, você arrasta um Grid para o layout e define as colunas (atributos). O GeneXus gera a query e a renderização automaticamente.
Colunas do Grid:
ClienteId | ClienteNome | ClienteEmail | ClienteStatus
Conditions (filtros do grid):
ClienteNome like &FiltroNome + "%" when not &FiltroNome.IsEmpty();
ClienteStatus = &FiltroStatus when not &FiltroStatus.IsEmpty();
As Conditions são o equivalente a cláusulas
WHEREdinâmicas. Owhenfunciona como um filtro condicional — só é aplicado se a variável não estiver vazia.
Exemplo Completo: Tela de Listagem com Filtro
Uma Web Panel WPClientes com:
- Campo de filtro por nome (
&FiltroNome) - Botão "Buscar"
- Grid com clientes filtrados
Layout (designer):
┌─────────────────────────────────────────┐
│ Filtro: [&FiltroNome ] [Buscar] │
├─────────────────────────────────────────┤
│ ID │ Nome │ Email │ Ação │
│ 1 │ João Silva │ joao@... │ [Ver]│
│ 2 │ Maria... │ maria@... │ [Ver]│
└─────────────────────────────────────────┘
Events:
Event Start
&FiltroNome = ""
EndEvent
Event 'Buscar'
Grid1.Search()
EndEvent
Event 'Ver'
ClienteDetalhe(ClienteId)
EndEvent
Conditions:
ClienteNome like &FiltroNome + "%" when not &FiltroNome.IsEmpty();
Na Interface — Criando uma Web Panel
- No KB Explorer, clique com botão direito → New → Web Panel
- Dê um nome (ex:
WPClientes) - Na aba Layout (designer visual):
- Arraste uma Table para organizar o layout
- Arraste campos de variáveis (
&FiltroNome) para criar inputs - Arraste um Button e defina o caption (ex: "Buscar")
- Arraste um Grid e adicione colunas (atributos:
ClienteId,ClienteNome, etc.)
- Na aba Events, escreva os event handlers
- Na aba Conditions, defina os filtros dinâmicos do grid
- Na aba Variables, defina variáveis locais usadas nos filtros e lógica
- Salve com Ctrl+S e execute com F5
Capítulo 9 — Work With Pattern
O que é o Work With Pattern?
O Work With Pattern é o scaffold/generator mais poderoso do GeneXus. A partir de uma Transaction, ele gera automaticamente uma aplicação completa com:
- Tela de listagem com paginação e ordenação
- Filtros de busca
- Formulário de edição/inserção
- Botões de ações (novo, editar, excluir)
- Navegação entre listagem e formulário
Se você já usou nest generate resource (NestJS) ou rails generate scaffold (Ruby on Rails), o conceito é similar — mas o Work With vai além, gerando a interface completa funcional.
O que é gerado automaticamente
Para a Transaction Cliente:
| Funcionalidade | Equivalente web tradicional |
|---|---|
| Tela de listagem | Página com <table> + paginação + filtros |
| Formulário de edição | Form com todos os campos + validação |
| Botão "Novo" | Link para form vazio |
| Botão "Editar" | Link para form preenchido |
| Botão "Excluir" | Ação com confirmação |
| Filtros por campo | Inputs de busca com query dinâmica |
| Ordenação por coluna | Click no header para sort |
Quando usar vs. quando customizar
- Use Work With para CRUDs padrão onde a interface gerada atende
- Customize quando precisar de layout diferente, lógica extra ou fluxo não convencional
- Use Web Panel manual quando a tela não é um CRUD (dashboards, relatórios, wizards)
Dica para devs: O Work With é um ponto de partida. Ele gera objetos Web Panel que você pode abrir e customizar depois. Não é uma caixa-preta.
Na Interface — Aplicando Work With Pattern
- Abra a Transaction desejada (ex:
Cliente) - No menu, vá em Work With → Apply Pattern
- O GeneXus gerará automaticamente:
WorkWithCliente— Web Panel principal (listagem)ClienteDetail— Web Panel de visualização- Formulários de inserção/edição
- Para customizar, abra os objetos gerados e modifique:
- Layout no designer visual
- Events para lógica customizada
- Conditions para filtros adicionais
- Execute com F5 para ver o resultado
Atalho: Você pode aplicar Work With a todas as Transactions de uma vez via Work With → Apply Pattern to All.
Capítulo 10 — APIs REST
Expondo APIs REST
No GeneXus, qualquer Procedure ou Data Provider pode ser exposto como endpoint REST. Não é preciso criar controllers, rotas ou middleware — basta configurar uma propriedade.
Exemplo lado a lado — Endpoint GET
Express.js (TypeScript):
// routes/clientes.ts
import { Router } from "express";
import { prisma } from "../db";
const router = Router();
router.get("/clientes", async (req, res) => {
const { status } = req.query;
const clientes = await prisma.cliente.findMany({
where: status ? { clienteStatus: String(status) } : undefined,
select: {
clienteId: true,
clienteNome: true,
clienteEmail: true,
},
});
res.json(clientes);
});
export default router;
GeneXus — Procedure APIListarClientes:
Rules:
Parm(in: &Status, out: &Clientes);
Source:
for each Cliente
where ClienteStatus = &Status when not &Status.IsEmpty()
&Cliente = new()
&Cliente.ClienteId = ClienteId
&Cliente.ClienteNome = ClienteNome
&Cliente.ClienteEmail = ClienteEmail
&Clientes.Add(&Cliente)
endfor
Propriedades do objeto:
Main Program: True
REST Protocol: True
HTTP Method: GET
REST URL: /api/clientes
Com essas configurações, o GeneXus expõe automaticamente:
GET /api/clientes?status=Ativo
Exemplo lado a lado — Endpoint POST
Express.js:
router.post("/clientes", async (req, res) => {
const { nome, email } = req.body;
try {
const cliente = await prisma.cliente.create({
data: { clienteNome: nome, clienteEmail: email, clienteStatus: "Ativo" },
});
res.status(201).json({ id: cliente.clienteId });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
GeneXus — Procedure APICriarCliente:
Rules:
Parm(in: &ClienteInput, out: &Resultado);
Source:
&Cliente = new()
&Cliente.ClienteNome = &ClienteInput.Nome
&Cliente.ClienteEmail = &ClienteInput.Email
&Cliente.ClienteStatus = "Ativo"
&Cliente.Save()
if &Cliente.Success()
&Resultado.Sucesso = true
&Resultado.Id = &Cliente.ClienteId
else
&Resultado.Sucesso = false
for &Msg in &Cliente.GetMessages()
&Resultado.Mensagem = &Msg.Description
endfor
endif
Propriedades:
REST Protocol: True
HTTP Method: POST
REST URL: /api/clientes
Consumindo APIs Externas
Para consumir APIs de terceiros, use o objeto HttpClient — equivalente ao fetch ou axios no TypeScript.
TypeScript com fetch:
const response = await fetch("https://viacep.com.br/ws/01001000/json/");
const data = await response.json();
console.log(data.logradouro); // "Praça da Sé"
GeneXus com HttpClient:
&HttpClient.Host = "viacep.com.br"
&HttpClient.BaseUrl = "/ws/"
&HttpClient.Secure = 1 // HTTPS
&HttpClient.Execute("GET", "01001000/json/")
if &HttpClient.StatusCode = 200
&JsonResponse = &HttpClient.ToString()
// Parsear JSON para SDT
&Endereco.FromJson(&JsonResponse)
msg(&Endereco.Logradouro) // "Praça da Sé"
else
msg("Erro: " + &HttpClient.StatusCode.ToString())
endif
| fetch/axios | HttpClient GeneXus |
|---|---|
fetch(url) |
&HttpClient.Execute("GET", path) |
response.status |
&HttpClient.StatusCode |
response.json() |
&HttpClient.ToString() + .FromJson() |
response.headers |
&HttpClient.GetHeader(name) |
| Request body | &HttpClient.AddString(json) |
| Headers | &HttpClient.AddHeader(name, value) |
Na Interface — Configurando APIs REST
Para expor uma API:
- Abra o Procedure ou Data Provider
- No painel Properties, configure:
- Main Program:
True - REST Protocol:
True - HTTP Method: GET, POST, PUT ou DELETE
- REST URL: o caminho do endpoint (ex:
/api/clientes)
- Main Program:
- Salve e faça Build
- O GeneXus gera automaticamente documentação Swagger/OpenAPI
- Acesse
{url-da-app}/swaggerpara testar os endpoints
Para consumir uma API:
- No Procedure, crie uma variável do tipo HttpClient
- Use os métodos
Host,BaseUrl,Execute,AddHeader,AddString - Crie um SDT para parsear a resposta JSON com
FromJson()
Capítulo 10.1 — APIs REST Avançado: Autenticação, Headers e Fluxo de Requisições
Este módulo aprofunda o trabalho com APIs REST no GeneXus, cobrindo cenários reais que todo desenvolvedor backend enfrenta: autenticação por token, headers customizados, middleware de validação, tratamento de erros HTTP e orquestração de fluxos.
Autenticação em APIs REST
Autenticação com Token Bearer (JWT)
No mundo Express/NestJS, autenticação por JWT segue este padrão:
Express.js (TypeScript):
// Middleware de autenticação
const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(401).json({ error: "Token não fornecido" });
}
const token = authHeader.split(" ")[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch {
return res.status(401).json({ error: "Token inválido" });
}
};
router.get("/pedidos", authMiddleware, pedidoController);
GeneXus — Procedure APIMiddlewareAuth:
No GeneXus, você captura o header Authorization usando o objeto HttpRequest (equivalente ao objeto req do Express):
// Procedure: APIMiddlewareAuth
// Rules: Parm(out: &UsuarioId, out: &Autorizado, out: &MensagemErro);
// Capturar o header Authorization
&HttpRequest.GetHeader("Authorization", &AuthHeader)
if &AuthHeader.IsEmpty()
&Autorizado = false
&MensagemErro = "Token não fornecido"
return
endif
// Extrair o token (remover "Bearer ")
if &AuthHeader.Substring(0, 7) = "Bearer "
&Token = &AuthHeader.Substring(7, &AuthHeader.Length() - 7)
else
&Autorizado = false
&MensagemErro = "Formato de token inválido. Esperado: Bearer <token>"
return
endif
// Validar o token contra o banco ou serviço de autenticação
&SessaoUsuario.Load(&Token) // BC que armazena sessões ativas
if &SessaoUsuario.Fail()
&Autorizado = false
&MensagemErro = "Token inválido ou expirado"
return
endif
// Verificar expiração
if &SessaoUsuario.SessaoExpiracao < &Now
&Autorizado = false
&MensagemErro = "Token expirado"
return
endif
&UsuarioId = &SessaoUsuario.UsuarioId
&Autorizado = true
Endpoint Protegido usando o Middleware
Express.js:
router.get("/pedidos", authMiddleware, async (req, res) => {
const pedidos = await getPedidos(req.user.id);
res.json(pedidos);
});
GeneXus — Procedure APIListarPedidosProtegido:
// Rules: Parm(out: &Resultado);
// Properties: REST Protocol = True, HTTP Method = GET, REST URL = /api/pedidos
// Chamar middleware de autenticação
APIMiddlewareAuth(&UsuarioId, &Autorizado, &MensagemErro)
if not &Autorizado
// Retornar HTTP 401
&HttpResponse.StatusCode = 401
&Resultado.Sucesso = false
&Resultado.Mensagem = &MensagemErro
return
endif
// Lógica do endpoint (usuário autenticado)
for each Pedido
where ClienteId = &UsuarioId
&Pedido = new()
&Pedido.PedidoId = PedidoId
&Pedido.PedidoData = PedidoData
&Pedido.PedidoStatus = PedidoStatus
&Pedido.PedidoTotal = PedidoTotal
&Resultado.Pedidos.Add(&Pedido)
endfor
&Resultado.Sucesso = true
| Express.js | GeneXus |
|---|---|
req.headers.authorization |
&HttpRequest.GetHeader("Authorization", &var) |
res.status(401) |
&HttpResponse.StatusCode = 401 |
req.user (do middleware) |
&UsuarioId (parâmetro de saída do middleware) |
| Middleware como função | Procedure chamado no início do endpoint |
Endpoint de Login (Gerar Token)
Express.js:
router.post("/auth/login", async (req, res) => {
const { email, senha } = req.body;
const usuario = await prisma.usuario.findUnique({ where: { email } });
if (!usuario || !bcrypt.compareSync(senha, usuario.senhaHash)) {
return res.status(401).json({ error: "Credenciais inválidas" });
}
const token = jwt.sign({ id: usuario.id }, process.env.JWT_SECRET, {
expiresIn: "24h",
});
res.json({ token, expiresIn: 86400 });
});
GeneXus — Procedure APILogin:
// Rules: Parm(in: &LoginInput, out: &LoginResponse);
// Properties: REST Protocol = True, HTTP Method = POST, REST URL = /api/auth/login
// Buscar usuário por email
for each Usuario
where UsuarioEmail = &LoginInput.Email
where UsuarioStatus = "Ativo"
// Validar senha (usando função de hash)
if HashSHA256(&LoginInput.Senha + UsuarioSalt) = UsuarioSenhaHash
// Gerar token único
&Token = NewGuid().ToString() + "-" + &Now.ToString()
&TokenHash = HashSHA256(&Token)
// Salvar sessão no banco
&Sessao = new()
&Sessao.UsuarioId = UsuarioId
&Sessao.SessaoToken = &TokenHash
&Sessao.SessaoExpiracao = &Now.AddHours(24)
&Sessao.Save()
if &Sessao.Success()
&LoginResponse.Sucesso = true
&LoginResponse.Token = &TokenHash
&LoginResponse.ExpiraEm = 86400
&LoginResponse.UsuarioNome = UsuarioNome
else
&HttpResponse.StatusCode = 500
&LoginResponse.Sucesso = false
&LoginResponse.Mensagem = "Erro ao criar sessão"
endif
return
else
&HttpResponse.StatusCode = 401
&LoginResponse.Sucesso = false
&LoginResponse.Mensagem = "Credenciais inválidas"
return
endif
endfor
// Se chegou aqui, email não encontrado
&HttpResponse.StatusCode = 401
&LoginResponse.Sucesso = false
&LoginResponse.Mensagem = "Credenciais inválidas"
Autenticação com GAM via API REST
Se você já habilitou o GAM (Capítulo 12), pode usar os serviços REST nativos dele em vez de implementar autenticação manual:
// Login via GAM — retorna token automaticamente
&GAMLoginResult = GAMRepository.Login(&Email, &Senha)
if &GAMLoginResult.Success
&Token = &GAMLoginResult.Token
// O GAM gera e valida tokens automaticamente
else
&HttpResponse.StatusCode = 401
&MensagemErro = &GAMLoginResult.ErrorMessage
endif
// Validar token via GAM em endpoints protegidos
&GAMSession = GAMRepository.GetSessionFromToken(&Token)
if &GAMSession.IsValid
&UsuarioId = &GAMSession.UserId
// Continuar com a lógica do endpoint
else
&HttpResponse.StatusCode = 401
endif
Quando usar GAM vs. autenticação manual: Use GAM quando quiser autenticação completa com roles, permissões, OAuth2 e backoffice pronto. Use autenticação manual quando precisar de controle total ou integrar com sistema de auth externo específico.
Headers Customizados
Recebendo Headers Customizados na API
Em APIs reais, é comum receber headers customizados como X-API-Key, X-Tenant-Id, X-Request-Id, X-Correlation-Id, etc.
Express.js:
router.get("/dados", (req, res) => {
const apiKey = req.headers["x-api-key"];
const tenantId = req.headers["x-tenant-id"];
const requestId = req.headers["x-request-id"] || uuidv4();
const contentType = req.headers["content-type"];
if (!apiKey || apiKey !== process.env.API_KEY) {
return res.status(403).json({ error: "API Key inválida" });
}
// Usar tenantId para filtrar dados por tenant
const dados = getDadosPorTenant(tenantId);
// Retornar headers customizados na resposta
res.set("X-Request-Id", requestId);
res.set("X-Response-Time", `${Date.now() - start}ms`);
res.json(dados);
});
GeneXus — Procedure APIDadosMultiTenant:
// Rules: Parm(out: &Resultado);
// Properties: REST Protocol = True, HTTP Method = GET, REST URL = /api/dados
// ===== RECEBER HEADERS =====
&HttpRequest.GetHeader("X-API-Key", &ApiKey)
&HttpRequest.GetHeader("X-Tenant-Id", &TenantId)
&HttpRequest.GetHeader("X-Request-Id", &RequestId)
&HttpRequest.GetHeader("Content-Type", &ContentType)
// Gerar Request-Id se não veio no header
if &RequestId.IsEmpty()
&RequestId = NewGuid().ToString()
endif
// ===== VALIDAR API KEY =====
if &ApiKey.IsEmpty() or &ApiKey <> &ApiKeyEsperada
&HttpResponse.StatusCode = 403
&Resultado.Sucesso = false
&Resultado.Mensagem = "API Key inválida"
// Mesmo em erro, retornar Request-Id para rastreabilidade
&HttpResponse.AddHeader("X-Request-Id", &RequestId)
return
endif
// ===== VALIDAR TENANT =====
if &TenantId.IsEmpty()
&HttpResponse.StatusCode = 400
&Resultado.Sucesso = false
&Resultado.Mensagem = "Header X-Tenant-Id é obrigatório"
&HttpResponse.AddHeader("X-Request-Id", &RequestId)
return
endif
// ===== LÓGICA DO ENDPOINT =====
&HoraInicio = Now()
for each Cliente
where ClienteTenantId = &TenantId
&Item = new()
&Item.ClienteId = ClienteId
&Item.ClienteNome = ClienteNome
&Resultado.Dados.Add(&Item)
endfor
&Resultado.Sucesso = true
// ===== RETORNAR HEADERS NA RESPOSTA =====
&HttpResponse.AddHeader("X-Request-Id", &RequestId)
&HttpResponse.AddHeader("X-Tenant-Id", &TenantId)
&HttpResponse.AddHeader("X-Total-Count", &Resultado.Dados.Count.ToString())
&HttpResponse.AddHeader("X-Response-Time", (&Now - &HoraInicio).ToString() + "ms")
Referência Completa: HttpRequest e HttpResponse
Lendo dados da requisição (HttpRequest):
Express.js (req) |
GeneXus (&HttpRequest) |
|---|---|
req.headers["x-custom"] |
&HttpRequest.GetHeader("X-Custom", &var) |
req.query.param |
&HttpRequest.GetVariable("param", &var) |
req.method |
&HttpRequest.Method |
req.url |
&HttpRequest.RequestURL |
req.ip |
&HttpRequest.RemoteAddress |
req.body (raw) |
&HttpRequest.ToString() |
req.cookies["name"] |
&HttpRequest.GetCookieValue("name") |
Escrevendo na resposta (HttpResponse):
Express.js (res) |
GeneXus (&HttpResponse) |
|---|---|
res.status(404) |
&HttpResponse.StatusCode = 404 |
res.set("Header", "val") |
&HttpResponse.AddHeader("Header", "val") |
res.type("application/json") |
&HttpResponse.ContentType = "application/json" |
res.cookie("name", "val") |
&HttpResponse.SetCookie("name", "val") |
res.redirect("/url") |
&HttpResponse.Redirect("/url") |
res.send(body) |
&HttpResponse.AddString(body) |
Enviando Headers em Requisições para APIs Externas
Quando seu GeneXus consome uma API externa que exige headers:
TypeScript com fetch:
const response = await fetch("https://api.externa.com/dados", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
"X-API-Key": "minha-chave-123",
"X-Correlation-Id": correlationId,
"Accept-Language": "pt-BR",
},
body: JSON.stringify({ filtro: "ativo" }),
});
GeneXus com HttpClient:
&HttpClient.Host = "api.externa.com"
&HttpClient.BaseUrl = "/dados"
&HttpClient.Secure = 1 // HTTPS
// Configurar headers
&HttpClient.AddHeader("Authorization", "Bearer " + &Token)
&HttpClient.AddHeader("Content-Type", "application/json")
&HttpClient.AddHeader("X-API-Key", "minha-chave-123")
&HttpClient.AddHeader("X-Correlation-Id", &CorrelationId)
&HttpClient.AddHeader("Accept-Language", "pt-BR")
// Body da requisição
&BodyJson = '{"filtro": "ativo"}'
&HttpClient.AddString(&BodyJson)
// Executar
&HttpClient.Execute("POST", "")
// Ler headers da resposta
&HttpClient.GetHeader("X-RateLimit-Remaining", &RateLimitRestante)
&HttpClient.GetHeader("X-Request-Id", &RequestIdExterno)
Fluxo de Requisições REST: Pipeline Completo
Arquitetura de Pipeline
Em aplicações reais, cada requisição REST passa por várias camadas antes de chegar à lógica de negócio. No Express.js isso é feito com middleware em cadeia. No GeneXus, o padrão é compor Procedures.
Express.js — Pipeline de middlewares:
router.post(
"/pedidos",
rateLimiter, // 1. Rate limiting
authMiddleware, // 2. Autenticação
validateBody(schema), // 3. Validação do body
logRequest, // 4. Logging
pedidoController // 5. Lógica de negócio
);
GeneXus — Pipeline equivalente com Procedures:
// Procedure: APICriarPedido
// Properties: REST Protocol = True, HTTP Method = POST, REST URL = /api/pedidos
// ===== 1. RATE LIMITING =====
APIRateLimiter(&HttpRequest, &Permitido, &MensagemRL)
if not &Permitido
&HttpResponse.StatusCode = 429
&HttpResponse.AddHeader("Retry-After", "60")
&Resultado.Sucesso = false
&Resultado.Mensagem = &MensagemRL
return
endif
// ===== 2. AUTENTICAÇÃO =====
APIMiddlewareAuth(&UsuarioId, &Autorizado, &MensagemAuth)
if not &Autorizado
&HttpResponse.StatusCode = 401
&Resultado.Sucesso = false
&Resultado.Mensagem = &MensagemAuth
return
endif
// ===== 3. VALIDAÇÃO DO BODY =====
&BodyJson = &HttpRequest.ToString()
&PedidoInput.FromJson(&BodyJson)
APIValidarPedidoInput(&PedidoInput, &Valido, &ErrosValidacao)
if not &Valido
&HttpResponse.StatusCode = 400
&Resultado.Sucesso = false
&Resultado.Mensagem = "Dados inválidos"
&Resultado.Erros = &ErrosValidacao
return
endif
// ===== 4. LOGGING =====
APILogRequest("POST", "/api/pedidos", &UsuarioId, &BodyJson)
// ===== 5. LÓGICA DE NEGÓCIO =====
CriarPedido(&PedidoInput, &UsuarioId, &PedidoCriado, &Sucesso, &MensagemNeg)
if &Sucesso
&HttpResponse.StatusCode = 201
&HttpResponse.AddHeader("Location", "/api/pedidos/" + &PedidoCriado.PedidoId.ToString())
&Resultado.Sucesso = true
&Resultado.Pedido = &PedidoCriado
else
&HttpResponse.StatusCode = 422
&Resultado.Sucesso = false
&Resultado.Mensagem = &MensagemNeg
endif
// ===== 6. LOG DE RESPOSTA =====
APILogResponse("POST", "/api/pedidos", &HttpResponse.StatusCode, &UsuarioId)
Procedure de Validação do Body
Zod (TypeScript):
const pedidoSchema = z.object({
clienteId: z.number().positive("ClienteId deve ser positivo"),
itens: z.array(z.object({
produtoId: z.number().positive(),
quantidade: z.number().min(1, "Quantidade mínima é 1"),
})).min(1, "Pedido deve ter pelo menos 1 item"),
});
// Uso no middleware
const data = pedidoSchema.parse(req.body); // throw se inválido
GeneXus — Procedure APIValidarPedidoInput:
// Rules: Parm(in: &PedidoInput, out: &Valido, out: &Erros);
&Valido = true
// Validar clienteId
if &PedidoInput.ClienteId <= 0
&Erro = new()
&Erro.Campo = "clienteId"
&Erro.Mensagem = "ClienteId deve ser positivo"
&Erros.Add(&Erro)
&Valido = false
endif
// Verificar se cliente existe
for each Cliente
where ClienteId = &PedidoInput.ClienteId
// Encontrou — ok
endfor
if &Valido and not found
&Erro = new()
&Erro.Campo = "clienteId"
&Erro.Mensagem = "Cliente não encontrado"
&Erros.Add(&Erro)
&Valido = false
endif
// Validar itens
if &PedidoInput.Itens.Count = 0
&Erro = new()
&Erro.Campo = "itens"
&Erro.Mensagem = "Pedido deve ter pelo menos 1 item"
&Erros.Add(&Erro)
&Valido = false
else
&ItemIndex = 0
for &Item in &PedidoInput.Itens
&ItemIndex += 1
if &Item.ProdutoId <= 0
&Erro = new()
&Erro.Campo = "itens[" + &ItemIndex.ToString() + "].produtoId"
&Erro.Mensagem = "ProdutoId deve ser positivo"
&Erros.Add(&Erro)
&Valido = false
endif
if &Item.Quantidade < 1
&Erro = new()
&Erro.Campo = "itens[" + &ItemIndex.ToString() + "].quantidade"
&Erro.Mensagem = "Quantidade mínima é 1"
&Erros.Add(&Erro)
&Valido = false
endif
endfor
endif
Procedure de Rate Limiting
// Procedure: APIRateLimiter
// Rules: Parm(in: &HttpRequest, out: &Permitido, out: &Mensagem);
&IP = &HttpRequest.RemoteAddress
&Agora = &Now
// Contar requisições do IP nos últimos 60 segundos
&ContadorReqs = 0
for each APIRateLimitLog
where RateLimitIP = &IP
where RateLimitTimestamp >= &Agora.AddSeconds(-60)
&ContadorReqs += 1
endfor
if &ContadorReqs >= 100 // limite: 100 req/min
&Permitido = false
&Mensagem = "Rate limit excedido. Tente novamente em 60 segundos."
return
endif
// Registrar esta requisição
new
RateLimitIP = &IP
RateLimitTimestamp = &Agora
RateLimitEndpoint = &HttpRequest.RequestURL
endnew
&Permitido = true
Procedure de Logging
// Procedure: APILogRequest
// Rules: Parm(in: &Metodo, in: &Endpoint, in: &UsuarioId, in: &Body);
new
LogTimestamp = &Now
LogMetodo = &Metodo
LogEndpoint = &Endpoint
LogUsuarioId = &UsuarioId
LogBody = &Body.Substring(0, 2000) // Limitar tamanho
LogTipo = "REQUEST"
endnew
Tratamento de Erros HTTP — Respostas Padronizadas
Padrão de Resposta de Erro
Em APIs profissionais, erros seguem uma estrutura consistente. No Express, normalmente se cria um error handler global.
Express.js:
// Error handler global
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
const status = err instanceof HttpException ? err.status : 500;
res.status(status).json({
sucesso: false,
erro: {
codigo: status,
mensagem: err.message,
timestamp: new Date().toISOString(),
path: req.url,
},
});
});
GeneXus — Procedure APIRetornarErro (helper reutilizável):
// Rules: Parm(in: &StatusCode, in: &Mensagem, in: &Detalhes, out: &ErroResponse);
&HttpResponse.StatusCode = &StatusCode
&ErroResponse.Sucesso = false
&ErroResponse.Erro.Codigo = &StatusCode
&ErroResponse.Erro.Mensagem = &Mensagem
&ErroResponse.Erro.Detalhes = &Detalhes
&ErroResponse.Erro.Timestamp = &Now.ToString()
&ErroResponse.Erro.RequestId = &RequestId
Uso nos endpoints:
// Em qualquer endpoint, reutilizar o helper
if not &Autorizado
APIRetornarErro(401, "Não autorizado", "Token expirado ou inválido", &Resultado)
return
endif
if not &Valido
APIRetornarErro(400, "Dados inválidos", &ErrosValidacao.ToJson(), &Resultado)
return
endif
// Erro interno inesperado
if &AlgoFalhou
APIRetornarErro(500, "Erro interno", "Falha ao processar pedido", &Resultado)
return
endif
SDT para resposta de erro padronizada:
SDT: APIErroResponse
Sucesso Boolean
Erro
Codigo Numeric(3)
Mensagem Character(500)
Detalhes Character(2000)
Timestamp Character(30)
RequestId Character(50)
Mapa de Status Codes
| Situação | Status Code | Quando usar |
|---|---|---|
| Sucesso | 200 | GET, PUT, PATCH com resposta |
| Criado | 201 | POST que cria recurso |
| Sem conteúdo | 204 | DELETE bem-sucedido |
| Dados inválidos | 400 | Validação do body falhou |
| Não autenticado | 401 | Token ausente ou inválido |
| Não autorizado | 403 | Sem permissão (API Key inválida, role insuficiente) |
| Não encontrado | 404 | Recurso não existe |
| Conflito | 409 | Duplicidade (email já existe) |
| Entidade não processável | 422 | Regra de negócio violada |
| Rate limit | 429 | Muitas requisições |
| Erro interno | 500 | Erro inesperado do servidor |
Consumindo API Externa com Autenticação Completa
Exemplo real: consumir uma API externa que exige OAuth2 Client Credentials (machine-to-machine).
TypeScript com axios:
// 1. Obter token
const tokenRes = await axios.post("https://auth.api.com/oauth/token", {
grant_type: "client_credentials",
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
scope: "read:dados",
});
const accessToken = tokenRes.data.access_token;
// 2. Usar token na requisição
const dadosRes = await axios.get("https://api.com/v1/dados", {
headers: {
Authorization: `Bearer ${accessToken}`,
"X-Request-Id": uuidv4(),
},
});
GeneXus — Procedure ConsumirAPIExternaOAuth:
// ===== PASSO 1: Obter Token OAuth2 =====
&HttpClientAuth.Host = "auth.api.com"
&HttpClientAuth.BaseUrl = "/oauth/"
&HttpClientAuth.Secure = 1
&HttpClientAuth.AddHeader("Content-Type", "application/x-www-form-urlencoded")
&AuthBody = "grant_type=client_credentials"
&AuthBody += "&client_id=" + &ClientId
&AuthBody += "&client_secret=" + &ClientSecret
&AuthBody += "&scope=read:dados"
&HttpClientAuth.AddString(&AuthBody)
&HttpClientAuth.Execute("POST", "token")
if &HttpClientAuth.StatusCode <> 200
msg("Erro ao obter token: " + &HttpClientAuth.StatusCode.ToString())
return
endif
// Parsear resposta do token
&TokenResponse.FromJson(&HttpClientAuth.ToString())
&AccessToken = &TokenResponse.AccessToken
// ===== PASSO 2: Chamar API com o Token =====
&HttpClient.Host = "api.com"
&HttpClient.BaseUrl = "/v1/"
&HttpClient.Secure = 1
&HttpClient.AddHeader("Authorization", "Bearer " + &AccessToken)
&HttpClient.AddHeader("X-Request-Id", NewGuid().ToString())
&HttpClient.AddHeader("Accept", "application/json")
&HttpClient.Execute("GET", "dados")
if &HttpClient.StatusCode = 200
&Dados.FromJson(&HttpClient.ToString())
// Processar dados...
else
if &HttpClient.StatusCode = 401
msg("Token expirado ou inválido")
else
msg("Erro na API: " + &HttpClient.StatusCode.ToString())
endif
endif
Na Interface — Configurando APIs REST Avançadas
Criando o SDT para requests/responses:
- No KB Explorer → botão direito → New → Structured Data Type
- Defina os campos aninhados (ex:
APIErroResponsecom sub-itemErro) - Para listas, marque a propriedade Collection no item
Configurando o HttpRequest/HttpResponse:
- Na aba Variables do Procedure, crie:
&HttpRequestdo tipo HttpRequest (para ler dados da requisição recebida)&HttpResponsedo tipo HttpResponse (para customizar a resposta)
- Esses objetos são preenchidos automaticamente pelo runtime quando o Procedure é chamado via REST
Testando com Swagger:
- Após Build, acesse
{url-da-app}/swagger - No Swagger, clique no botão Authorize para configurar o token Bearer
- Teste cada endpoint com headers customizados via a interface do Swagger
- Verifique os headers de resposta na seção "Response headers"
Testando com ferramentas externas:
- Use Postman ou Insomnia para testar endpoints com headers customizados
- Configure uma Collection no Postman com variáveis de ambiente para token e API Key
- Use a aba Pre-request Script no Postman para automatizar a obtenção do token antes de cada request
Capítulo 11 — Deploy e Ambientes
Generators
No GeneXus, um Generator define a tecnologia-alvo para a qual seu código será gerado. É o equivalente a escolher seu stack de deploy.
| Decisão tradicional | Generator GeneXus |
|---|---|
| Backend Node.js/Express | Generator Java ou .NET |
| Frontend React/Angular | Generator Angular ou Web (server-side) |
| Mobile React Native | Generator Android / iOS |
tsconfig.json (target) |
Propriedades do Generator |
Os generators mais comuns no GeneXus 18:
- Java — gera aplicação Java (Tomcat, Spring)
- .NET — gera aplicação .NET (IIS, Kestrel)
- .NET Core — gera aplicação .NET 6+
- Angular — gera frontend Angular
O mesmo modelo (KB) pode gerar para múltiplas plataformas. Você define Transactions e lógica uma vez, e gera para Java e Angular ao mesmo tempo.
Environments
Um Environment no GeneXus é uma configuração completa de deploy — equivalente a arquivos .env + configuração por ambiente no mundo Node.js.
Node.js tradicional:
.env.development → DB local, debug ligado
.env.staging → DB de staging, logs ativados
.env.production → DB de produção, otimizações
GeneXus — Environments:
Cada Environment define:
- Generator (Java, .NET, Angular)
- DBMS (SQL Server, MySQL, PostgreSQL, Oracle)
- String de conexão com o banco
- URL base da aplicação
- Configurações de deploy (servidor, porta, etc.)
Você pode ter:
Desenvolvimento→ SQL Server local, .NET, debugHomologação→ PostgreSQL em servidor de stagingProdução→ SQL Server em cloud, otimizado
Build e Reorganize
No mundo Node.js/TypeScript, o fluxo de build é:
npm run build # compila TypeScript → JavaScript
npx prisma migrate # aplica migrations no banco
npm start # inicia a aplicação
No GeneXus, os equivalentes são:
| Comando Node.js | GeneXus |
|---|---|
npm run build |
Build → Build All |
prisma migrate / typeorm migration:run |
Build → Reorganize (aplica alterações no banco) |
npm start |
Build → Run (F5) |
- Build All — gera o código-fonte (Java/.NET) a partir do modelo
- Reorganize — compara o modelo atual com o banco e aplica as alterações (cria tabelas, adiciona colunas, etc.) — como uma migration automática
- Run (F5) — compila e executa a aplicação
Para devs SQL: O Reorganize é como se o GeneXus gerasse e aplicasse migrations automaticamente. Ele nunca perde dados — se uma coluna muda de tipo, ele faz a conversão.
Deploy em Cloud
O GeneXus 18 suporta deploy em:
- GeneXus Cloud — plataforma PaaS da GeneXus (mais simples)
- AWS, Azure, GCP — via empacotamento do artefato gerado
- Docker — o código gerado pode ser containerizado
- Tomcat/IIS — deploy tradicional em servidores
Na Interface — Configurando Environments e Generators
- No KB Explorer, expanda o nó Preferences
- Você verá os Environments existentes (ex:
Default) - Para criar um novo Environment:
- Botão direito em Preferences → New → Environment
- Defina nome, Generator e DBMS
- Para configurar um Environment:
- Expanda o Environment e clique no Generator (ex:
.NET) - No painel Properties, configure:
- Data Store: conexão com o banco
- Server name, Database name: dados de conexão
- Target path: onde o código gerado será salvo
- Expanda o Environment e clique no Generator (ex:
- Para executar Build/Reorganize:
- Menu Build → Build All (ou Ctrl+F5)
- Menu Build → Reorganize (para aplicar mudanças no banco)
- Menu Build → Run (ou F5) para executar
Capítulo 12 — Segurança (GAM)
O que é o GAM?
O GeneXus Access Manager (GAM) é o módulo de segurança integrado do GeneXus. Ele fornece autenticação, autorização, gestão de usuários e controle de acesso — tudo que no mundo Node.js você faria com Passport.js, NextAuth, JWT e middleware customizado.
| Conceito Node.js | GAM GeneXus |
|---|---|
| Passport.js / NextAuth | GAM (autenticação) |
| JWT / Session tokens | GAM tokens |
Middleware isAuthenticated |
GAM integrado (automático) |
| Roles (admin, user, etc.) | GAM Roles |
| Permissões por rota | GAM Permissions por objeto |
| Tabelas de usuário customizadas | GAM User / GAM Role (tabelas gerenciadas) |
| OAuth2 (Google, GitHub) | GAM Authentication Types |
O que o GAM fornece
- Login/Logout com tela pronta
- Gestão de usuários (criar, editar, bloquear)
- Roles e permissões (quem acessa o quê)
- Autenticação externa (OAuth2 com Google, Facebook, etc.)
- Tokens de sessão (similar a JWT)
- Auditoria (logs de acesso)
Autenticação
Quando o GAM está habilitado, todas as páginas são protegidas automaticamente — como um middleware global de autenticação.
Express.js com middleware:
// Middleware global
app.use(authMiddleware);
// Ou por rota
router.get("/admin", requireRole("admin"), adminController);
GeneXus com GAM: Após habilitar o GAM, todas as Web Panels e APIs exigem autenticação automaticamente. Para tornar algo público, você marca explicitamente.
Roles e Permissões
Node.js (manual):
// Verificar role no código
if (user.role !== "admin") {
throw new ForbiddenException("Acesso negado");
}
GeneXus com GAM: Roles e permissões são configurados na interface administrativa do GAM (um backoffice gerado automaticamente). Você associa permissões a objetos GeneXus e atribui roles aos usuários — sem código.
Na Interface — Habilitando e Configurando GAM
- No menu, vá em Knowledge Manager → GeneXus Access Manager
- Selecione Enable GAM → o GeneXus adiciona os objetos de segurança à KB
- Faça Build → Build All e Reorganize para criar as tabelas do GAM no banco
- Execute a aplicação — a tela de login aparecerá automaticamente
- Use o GAM Backoffice (gerado automaticamente) para:
- Criar usuários (admin padrão:
admin/admin123) - Criar roles (ex: Administrador, Vendedor, Cliente)
- Atribuir permissões (quais objetos cada role pode acessar)
- Configurar autenticação externa (Google, Facebook, etc.)
- Criar usuários (admin padrão:
- Para tornar um objeto público (sem login):
- Abra o objeto → Properties → GAM Object Visibility →
Public
- Abra o objeto → Properties → GAM Object Visibility →
Nota: O GAM é um módulo robusto com muitas configurações. Para a maioria dos projetos, habilitar o GAM e configurar roles no backoffice é suficiente. Configurações avançadas (OAuth2, SAML, federação) estão documentadas no wiki oficial.
Capítulo 13 — Projeto Prático: Sistema de Vendas
Agora vamos aplicar tudo que aprendemos construindo um sistema completo de vendas. Cada passo referencia os capítulos anteriores.
Modelo de Dados (Cap. 2-3)
Transaction Cliente
{ClienteId* Numeric(4) Autonumber}
ClienteNome Character(100)
ClienteEmail Character(200)
ClienteTelefone Character(20)
ClienteStatus Character(10) // Domínio: Status ("Ativo", "Inativo")
Rules:
ClienteNome.Required("Nome é obrigatório");
ClienteEmail.Required("Email é obrigatório");
Default(ClienteStatus, "Ativo");
Transaction Produto
{ProdutoId* Numeric(4) Autonumber}
ProdutoNome Character(100)
ProdutoPreco Numeric(10.2) // Domínio: Preco
ProdutoEstoque Numeric(6)
ProdutoStatus Character(10)
Rules:
ProdutoNome.Required("Nome é obrigatório");
Error("Preço deve ser maior que zero") if ProdutoPreco <= 0;
Default(ProdutoStatus, "Ativo");
Default(ProdutoEstoque, 0);
Transaction Pedido com nível PedidoItem
{PedidoId* Numeric(4) Autonumber}
ClienteId Numeric(4) // FK → Cliente
ClienteNome Character(100) // Inferido (somente leitura)
PedidoData Date
PedidoStatus Character(15) // "Pendente", "Aprovado", "Cancelado"
PedidoTotal Numeric(12.2)
// Nível: Itens do pedido
{PedidoItemId* Numeric(4) Autonumber}
ProdutoId Numeric(4) // FK → Produto
ProdutoNome Character(100) // Inferido
ProdutoPreco Numeric(10.2) // Inferido
PedidoItemQtd Numeric(6)
PedidoItemSubtotal Numeric(12.2)
Rules:
ClienteId.Required("Selecione um cliente");
Default(PedidoData, &Today);
Default(PedidoStatus, "Pendente");
Noaccept(PedidoTotal);
Noaccept(PedidoItemSubtotal);
Noaccept(ClienteNome);
Noaccept(ProdutoNome);
Noaccept(ProdutoPreco);
// Cálculos automáticos
PedidoItemSubtotal = PedidoItemQtd * ProdutoPreco;
PedidoTotal = sum(PedidoItemSubtotal);
// Validações
Error("Quantidade deve ser maior que zero") if PedidoItemQtd <= 0;
Error("Selecione um produto") if ProdutoId.IsEmpty();
Lógica de Backend (Cap. 5, 7)
Procedure AprovarPedido
Rules:
Parm(in: &PedidoId, out: &Sucesso, out: &Mensagem);
Source:
// Usar Business Component para atualizar
&Pedido.Load(&PedidoId)
if &Pedido.Fail()
&Sucesso = false
&Mensagem = "Pedido não encontrado"
return
endif
if &Pedido.PedidoStatus <> "Pendente"
&Sucesso = false
&Mensagem = "Pedido não está pendente"
return
endif
// Verificar estoque de cada item
for each PedidoItem
where PedidoId = &PedidoId
if PedidoItemQtd > ProdutoEstoque
&Sucesso = false
&Mensagem = "Estoque insuficiente para: " + ProdutoNome
return
endif
endfor
// Aprovar e baixar estoque
&Pedido.PedidoStatus = "Aprovado"
&Pedido.Save()
// Baixar estoque de cada item
for each PedidoItem
where PedidoId = &PedidoId
&Produto.Load(ProdutoId)
&Produto.ProdutoEstoque = &Produto.ProdutoEstoque - PedidoItemQtd
&Produto.Save()
endfor
&Sucesso = true
&Mensagem = "Pedido aprovado com sucesso"
Procedure RelatorioPedidosPorPeriodo
Rules:
Parm(in: &DataInicio, in: &DataFim, out: &Relatorio);
Source:
for each Pedido
where PedidoData >= &DataInicio
where PedidoData <= &DataFim
order PedidoData
&Linha = new()
&Linha.PedidoId = PedidoId
&Linha.ClienteNome = ClienteNome
&Linha.PedidoData = PedidoData
&Linha.PedidoStatus = PedidoStatus
&Linha.PedidoTotal = PedidoTotal
&Relatorio.Add(&Linha)
endfor
Interface (Cap. 8-9)
Work With para CRUD
Aplique Work With Pattern nas Transactions Cliente e Produto para gerar CRUDs completos automaticamente.
Web Panel customizada: Dashboard de Pedidos
// WPDashboard - Events
Event Start
&DataInicio = &Today.AddMonths(-1)
&DataFim = &Today
EndEvent
Event 'Filtrar'
Grid1.Search()
EndEvent
Event 'Aprovar'
AprovarPedido(PedidoId, &Sucesso, &Mensagem)
if &Sucesso
msg(&Mensagem)
Grid1.Search() // Recarregar lista
else
msg(&Mensagem)
endif
EndEvent
Conditions do Grid:
PedidoData >= &DataInicio when not &DataInicio.IsEmpty();
PedidoData <= &DataFim when not &DataFim.IsEmpty();
PedidoStatus = &FiltroStatus when not &FiltroStatus.IsEmpty();
API REST (Cap. 10)
Exponha os Procedures como API:
| Endpoint | Procedure | Método |
|---|---|---|
GET /api/pedidos |
RelatorioPedidosPorPeriodo |
GET |
POST /api/pedidos/aprovar |
AprovarPedido |
POST |
GET /api/clientes |
APIListarClientes |
GET |
POST /api/clientes |
APICriarCliente |
POST |
Checklist Final
- Transactions criadas: Cliente, Produto, Pedido
- Rules de validação em todas as Transactions
- Business Components habilitados em Cliente, Produto, Pedido
- Procedures de lógica: AprovarPedido, RelatorioPedidosPorPeriodo
- Work With aplicado em Cliente e Produto
- Web Panel customizada: Dashboard de Pedidos
- APIs REST expostas
- Build + Reorganize executados
- Teste: criar cliente, produto, pedido com itens, aprovar pedido
Capítulo 14 — Próximos Passos
Recursos Avançados
Após dominar os fundamentos cobertos neste ebook, explore:
- Panels para Mobile — gere apps nativos Android/iOS a partir do mesmo modelo
- GeneXus BPM — fluxos de trabalho (workflows) para processos de negócio
- GeneXus BI — dashboards e relatórios analíticos integrados
- Chatbots/Conversational — crie assistentes conversacionais com NLP integrado
- Módulos e Marketplace — reutilize componentes prontos da comunidade
Comunidade e Documentação
- GeneXus Wiki — documentação oficial com exemplos detalhados de cada objeto e propriedade
- GeneXus Training — cursos online oficiais (gratuitos e pagos)
- GeneXus Community — fórum com perguntas e respostas da comunidade
- Encuentro GeneXus — conferência anual com workshops e palestras
- YouTube GeneXus — vídeos tutoriais e webinars
Certificação GeneXus
A GeneXus oferece certificações oficiais que validam seu conhecimento:
- GeneXus Analyst — nível inicial, cobre Transactions, Rules, Procedures
- GeneXus Senior Analyst — nível avançado, cobre arquitetura, performance, deploy
- GeneXus Technical — focado em configuração de ambientes e infraestrutura
Dica final: O maior erro de quem vem de linguagens tradicionais é tentar "programar" tudo no GeneXus como faria em TypeScript. A mentalidade correta é: declare o que você quer, não como fazer. Deixe o GeneXus resolver a implementação. Quanto mais você confia no modelo declarativo, mais produtivo se torna.
Comentários
Postar um comentário