Pitch: De workflows visuais a código nativo em produção: a jornada completa de um backend n8n
Fala pessoal do TabNews! Acabei de publicar esse relato no meu LinkedIn sobre como resolvi um gargalo brutal de latência no meu SaaS, mas achei que a comunidade aqui ia curtir a engenharia por trás disso.
Vou contar essa história na ordem que aconteceu. Não começa com um compilador. Começa com dois servidores n8n e um problema de sincronização que eu precisava resolver antes de conseguir dormir tranquilo.
Ato 1 — O problema que ninguém conta quando te recomendam o n8n
O CoreAutoCRM é um SaaS de gestão de oficinas mecânicas que opera inteiramente pelo WhatsApp. Atendimento por IA, agendamento, orçamentos, gestão de pátio, follow-ups inteligentes — tudo isso vive em workflows do n8n. Escolhi o n8n pela razão óbvia: velocidade. Em meses fui do zero a um produto em produção real, construído sozinho.
O n8n é ótimo para isso. O problema começa quando você tem um produto real, com um cliente real, e precisa de dois ambientes: um para testar sem medo de quebrar o que está funcionando, e outro para o que o cliente usa de verdade.
Staging e produção. Dois servidores n8n separados.
E aí aparece o problema que ninguém conta: como você garante que o que você testou em staging é exatamente o que vai para produção?
No n8n, um workflow vive dentro do banco de dados do servidor. Não é um arquivo. Não tem histórico. Não tem diff. Se você abre o editor visual e muda uma condição de um Switch, um parâmetro de uma query SQL, ou o prompt de IA de um nó — essa mudança existe só na UI daquele servidor. Não há como saber o que mudou, quem mudou, ou quando. Não há como reverter. Não há como revisar antes de ir para produção.
A forma "manual" de sincronizar staging com produção seria: abrir cada workflow no staging, exportar o JSON, abrir o mesmo workflow na produção, importar o JSON. Com 74 workflows, isso é inviável. E mesmo se fosse viável, seria um processo manual sujeito a erro humano — o tipo de coisa que te acorda às 2 da manhã porque alguém (você mesmo) esqueceu de sincronizar um workflow crítico.
Precisava de uma solução que:
- Capturasse o estado atual de todos os workflows do n8n como arquivos
- Versionasse esses arquivos no Git como qualquer código
- Automatizasse a publicação de alterações aprovadas diretamente no servidor de produção
Não existia isso pronto. Construí do zero.
Ato 2 — GitOps para n8n: como transformei workflows visuais em código versionável
A API REST do n8n tem endpoints para tudo: listar workflows, buscar pelo JSON de um workflow específico, criar, atualizar, ativar, desativar. O que eu precisava era de uma automação que usasse essa API nos dois sentidos.
Construí uma skill de sincronização que faz o seguinte:
Direção 1 — n8n → Git (download):
A automação se conecta ao servidor n8n de staging via API, baixa o JSON de cada workflow, organiza em uma estrutura de pastas no projeto local (/agente/workflows/), e prepara um merge request com as alterações. Posso fazer isso a qualquer momento — depois de um ciclo de desenvolvimento no editor visual, quando quero "commitar" o estado atual do backend.
Direção 2 — Git → n8n (upload):
O inverso também funciona. Posso editar um JSON diretamente no repositório — com assistência de IA, já que é só texto estruturado — e publicar essa alteração de volta no servidor n8n. Para ajustes pontuais, às vezes é mais rápido editar o JSON do que navegar na interface visual.
O pipeline de CI/CD:
Quando um merge request é aprovado no Git, o pipeline de CI/CD entra em ação automaticamente. Ele usa a API do n8n de produção para publicar cada workflow alterado diretamente no servidor de produção, sem intervenção manual. O que foi testado em staging é exatamente o que chega à produção — porque é o mesmo JSON, versionado, revisado, aprovado.
O resultado foi uma estrutura que eu não esperava que ficasse tão boa: desenvolvimento de backend visual com disciplina de engenharia de software tradicional. Cada feature de backend virava um branch. Cada alteração tinha diff legível no Git. Cada deploy tinha rastreabilidade completa. E tudo isso sem abrir mão do editor visual do n8n, que continuava sendo a ferramenta mais produtiva para construir e testar fluxos rapidamente.
Com essa estrutura funcionando, algo importante aconteceu silenciosamente: eu passei a ter 74 workflows — todo o backend do produto naquele momento — como arquivos JSON estruturados, atualizados, versionados, e sincronizados no repositório. O código-fonte do backend existia em formato legível por máquina.
Eu ainda não sabia o que fazer com isso além de versionamento. Mas estava prestes a descobrir.
Ato 3 — O problema que o GitOps não resolve: velocidade e escala
A estrutura de GitOps resolveu governança completamente. Nunca mais perdi rastreabilidade de uma alteração. Nunca mais tive medo de sincronizar staging com produção. O pipeline funcionava lindamente.
Enquanto isso, o produto continuou crescendo. Os 74 workflows originais viraram 126 — novas features, novos módulos de IA, novos fluxos operacionais. Tudo versionado, tudo sincronizado, tudo passando pelo mesmo pipeline. A estrutura de GitOps escalonava naturalmente com o crescimento do produto.
Mas o n8n continuava sendo o runtime de produção. E conforme o volume cresceu, o custo disso ficou mais visível.
Cada nó do n8n serializa e desserializa o estado completo entre execuções. É o custo inerente de qualquer engine de orquestração visual que precisa ser genérico o suficiente para servir a todo mundo. Cada chamada de subworkflow — e eu tinha muitas, porque minha arquitetura de microsserviços no n8n usava subworkflows ativamente — virava uma chamada HTTP interna com autenticação, serialização e overhead de transporte. Resultado: 180ms a 450ms de latência média, 1,2 GB a 2,5 GB de RAM por instância.
Para um SaaS que atende mensagens de WhatsApp em tempo real, isso é um problema real. A sensação de "demora para responder" começa a aparecer no produto antes de você ter volume suficiente para justificar uma reescrita tradicional.
As opções convencionais:
Reescrever em código nativo. Seis meses de trabalho, pausa total em novas features, migração arriscada, e — mais importante — fim da velocidade de desenvolvimento visual que me deixou chegar onde cheguei. Descartei.
Escalar horizontalmente com mais instâncias de n8n. Multiplica um custo que já é alto, sem resolver a latência por requisição. Descartei.
E então a percepção que mudou tudo: eu já tinha os 74 workflows como JSON estruturado no repositório. Se existe um programa capaz de ler esses JSONs e entender o que cada workflow faz — o que cada nó recebe, processa e produz — esse programa pode gerar código TypeScript equivalente. Código que não tem serialização entre nós. Que não faz chamadas HTTP internas. Que roda direto no processo, sem engine de orquestração no meio.
O GitOps que eu construí para resolver versionamento havia, sem querer, criado o pré-requisito para o próximo passo: um compilador.
O compilador — e por que cada decisão foi forçada por um problema real
Problema 1: a ordem dos nós no JSON não é a ordem de execução
A primeira versão do compilador seguia os nós na ordem em que aparecem no JSON. Quebrou imediatamente.
Workflows visuais não têm ordem linear. Um nó de autenticação JWT pode aparecer na posição 3 no JSON, mas precisa executar antes do nó de banco de dados na posição 1. Um nó If ativa apenas um dos dois ramos. Um nó que recebe entrada de dois ramos diferentes precisa esperar que pelo menos um deles tenha executado.
Workflows visuais são Grafos Direcionados Acíclicos — DAGs. A única forma de resolver a ordem de execução corretamente é com ordenação topológica.
Usei o Algoritmo de Kahn: começa pelos nós sem predecessores (os triggers), processa-os, marca-os como resolvidos, libera os nós que dependiam deles, repete. O resultado é uma fila linear que garante que todo nó só execute depois que todos os seus predecessores já executaram.
O compilador também identifica nós SINK — nós que terminam a execução e devolvem a resposta HTTP (Respond to Webhook). Esses nós ficam no final da fila topológica, permitindo que a resposta ao cliente seja devolvida em menos de 5ms enquanto tarefas assíncronas secundárias continuam em background.
Problema 2: subworkflows em memória e o bug silencioso do tree-shaking
Minha primeira implementação de subworkflows usava importações ES Module normais. Funcionou no desenvolvimento. Quebrou silenciosamente no bundle de produção.
O problema tem duas partes.
Primeiro: módulos ES com caminhos relativos de profundidades diferentes podem resultar em instâncias de Map separadas no mesmo processo. Dois módulos que acham que compartilham o mesmo registry podem estar falando com registries diferentes sem nenhum erro visível.
Segundo: bundlers como bun build e esbuild fazem tree-shaking — removem código não referenciado na análise estática. Subworkflows chamados dinamicamente pelo nome (executeSubworkflow("COREAUTOCRM-PANEL-ACTION-GET-OS-DETAILS", ...)) são invisíveis para o bundler. O nome é uma string em runtime. O bundler não sabe que aquela string corresponde a uma função. O bundle chegava à produção sem os subworkflows, e as chamadas dinâmicas falhavam silenciosamente.
Solução para os dois problemas ao mesmo tempo: Global Singleton Registry atrelado ao globalThis.
const g = globalThis as any;
if (!g.__COREAUTO_WORKFLOWS_REGISTRY__) {
g.__COREAUTO_WORKFLOWS_REGISTRY__ = new Map<string, Function>();
}
export const workflowsRegistry = g.__COREAUTO_WORKFLOWS_REGISTRY__;
globalThis é garantidamente único por processo. Para resolver o tree-shaking, o compilador injeta automaticamente uma exportação estática que referencia todos os 126 workflows, forçando o bundler a incluir todos no bundle.
Problema 3: o parser de expressões foi o mais trabalhoso
O n8n usa uma sintaxe de expressão própria: ={{ $json.body.osId }}, ={{ $('NomeDoNo').item.json.campo }}, Texto: {{ $json.nome }}. Converter isso para TypeScript tem três problemas distintos.
O primeiro é safe chaining. $json.user.loja_id quebra com TypeError se user for nulo. O compilador precisa converter para item?.json?.user?.loja_id para caminhos de profundidade arbitrária.
O segundo é mistura de texto e expressão. "Texto: {{ $json.nome }}" precisa virar `Texto: ${item?.json?.nome ?? ''}` — template string com fallback.
O terceiro eu não esperava: os prompts de IA. Os prompts que alimentam o Gemini nos workflows contêm blocos de código Markdown com crases triplas (```json). Quando o compilador coloca esses prompts dentro de template strings TypeScript, as crases internas quebram a sintaxe. O parser precisou aprender a identificar esses tokens e escapar cada crase interna com ```.
A solução foi um tokenizador real — não regex sobre o texto inteiro, mas um parser que identifica os delimitadores {{ e }}, extrai o conteúdo de expressão, converte a gramática do n8n para TypeScript, e reconstrói o texto com escaping adequado para cada contexto.
Problema 4: como garantir que o código gerado faz o mesmo que o n8n
Gerar código que compila não é suficiente. Preciso garantir que o código gerado produz exatamente o mesmo resultado que o n8n produziria com os mesmos dados.
A API do n8n expõe o histórico completo de execuções: GET /api/v1/executions. Para cada execução, ela retorna payload de entrada, dados intermediários de cada nó, e payload de saída final.
O TestGenerator usa essa API como oráculo: executo um workflow no n8n de staging com dados reais, o gerador busca essa execução, captura input e output de cada nó, e gera um arquivo .test.ts que verifica que a função TypeScript compilada produz output idêntico, campo a campo.
Se qualquer campo divergir, o teste falha e o CI bloqueia o deploy. O n8n de staging não é só a IDE visual — é o oráculo de correctitude de tudo que vai para produção.
Os números, sem marketing
| Métrica | n8n em VPS | Fastify compilado | Diferença |
|---|---|---|---|
| Latência média | 180ms – 450ms | 3ms – 8ms | ~35x mais rápido |
| RAM por instância | 1,2 GB – 2,5 GB | 80 MB – 120 MB | ~95% menos |
| Throughput por vCPU | ~120 req/s | > 4.500 req/s | ~37x mais escala |
| Tempo de compilação (126 workflows) | — | 77ms | Instantâneo |
O bundle de produção tem 2,6 MB. Sobe com PM2, fica atrás de Nginx, deploy automatizado pelo CI quando todos os testes passam.
O que essa jornada inteira significa
Olhando para trás, o que aconteceu foi uma sequência onde cada problema resolvido criou as condições para o próximo passo:
O problema de sincronizar dois servidores n8n me forçou a construir a automação de GitOps. O GitOps me deu 74 workflows como JSONs versionados no repositório. Os JSONs no repositório criaram o pré-requisito para o compilador. O compilador transformou esses JSONs em código nativo com garantia automática de correctitude.
Nenhum desses passos foi planejado desde o início. Cada um resolveu um problema real e criou, como efeito colateral, a infraestrutura que o próximo passo precisava.
Hoje o fluxo completo é:
- Desenvolvo visualmente no n8n de staging — com toda a velocidade que o Low-code oferece
- A skill de sincronização baixa os workflows como JSON e abre um merge request no Git
- O CI/CD aprova e publica em produção no n8n — o ambiente operacional continua funcionando
- Em paralelo,
bun run transpilarcompila os JSONs em TypeScript nativo - Os testes gerados automaticamente verificam equivalência campo a campo com execuções reais do n8n
- Se todos passam, o bundle Fastify vai para a VPS
O n8n continua sendo a ferramenta de desenvolvimento. O Fastify é o runtime de produção. Os JSONs no repositório são o contrato que conecta os dois — e o mesmo contrato que, desde o Ato 2, me deu governança, rastreabilidade e paz de espírito.
Startup enxuta não é aquela que escolhe entre velocidade e qualidade. É a que constrói a infraestrutura para ter as duas — e, com sorte, descobre que cada problema resolvido era o degrau para o próximo.
O CoreAutoCRM já roda em produção em dezenas de oficinas pelo Brasil. Eu construí essa engenharia para o meu próprio ecossistema, mas sei que a dor de escalar automações em servidores engessados tira o sono de muito CTO e fundador por aí.
O meu foco principal é a expansão do meu SaaS, o que torna minha agenda técnica quase inexistente. No entanto, decidi abrir uma janela de tempo exclusiva neste mês para estruturar a arquitetura de apenas 2 operações que precisam resolver esse exato problema: escalar o n8n para milhões de requisições, reduzir o custo de AWS/VPS em 90% e derrubar a latência para a casa dos milissegundos.
Se o seu n8n está consumindo a memória do seu servidor, se a sua automação trava em horário de pico, ou se você simplesmente bateu no teto do Low-Code e não quer reescrever tudo do zero, me chame no whatsapp (https://wa.me/556296232227). Vamos fazer um raio-x rápido da sua infraestrutura e resolver isso.