O problema
Um SaaS de delivery precisa atender três públicos com ritmos muito diferentes: o restaurante, o entregador e o cliente final. Tratar isso como um monolito significa acoplar o pico de pedidos do almoço, o tracking constante da entrega e a explosão de pagamento no fechamento na mesma base e no mesmo deploy. A meta foi separar os domínios de forma limpa, para escalar e evoluir cada parte sem que uma pise no calo da outra.
Arquitetura: sete serviços atrás de um gateway
O backend são sete serviços NestJS, cada um dono do próprio banco e do próprio contrato, atrás de um API gateway que é a porta única para os clientes. Duas pontas consomem essa porta: o backoffice web e o app mobile.
backoffice web (React) app mobile (Expo / React Native)
└───────────────┬────────────────┘
API gateway
│
┌──────── 7 serviços NestJS, cada um com seu banco ────────┐
auth sessões, refresh, rotação de token
user perfis, endereços, papéis
catalog restaurantes, cardápios, itens (cache em Redis)
order máquina de estado do pedido (Postgres + MongoDB)
payment provedores, liquidação, estornos
notification e-mail, push, in-app
delivery atribuição de entregador, ETA, comprovante
eventos via Kafka · status do pedido em tempo real via WebSocket
A comunicação assíncrona entre serviços é orientada a eventos, com Apache Kafka. Quando um pedido muda de estado ou um pagamento é processado, o serviço publica um evento em um tópico (order.status.changed, payment.processed e afins), e quem se importa consome. Isso desacopla os serviços no tempo: o pagamento não precisa saber quem reage a ele.
O serviço de pedido é o mais interessante. O estado do pedido vive no Postgres como fonte da verdade (PENDING → CONFIRMED → PREPARING → READY → PICKED_UP → IN_DELIVERY → DELIVERED, com CANCELLED e REFUNDED), e o histórico completo de transições vai para o MongoDB, melhor para uma linha do tempo append-only de auditoria. Em cima disso, um gateway WebSocket com Socket.io escuta os eventos do Kafka e empurra a mudança de status em tempo real para quem está acompanhando o pedido. Cada serviço expõe REST documentada via Swagger.
O aprendizado é sobre fronteira: o evento é o que mantém os serviços de fato independentes. No momento em que um serviço precisa chamar o outro de forma síncrona para um efeito colateral, o acoplamento volta pela porta dos fundos.
O backoffice web
O painel administrativo é um produto por si só, e foi onde passei boa parte do tempo. É uma SPA em React 19 com Vite e TanStack Router, com roteamento file-based e divisão de código automática. A organização é fatiada por domínio: cada feature (pedidos, restaurantes, entregadores, pagamentos, promoções, usuários, suporte, ajustes e mais) é autocontida, com suas páginas, componentes, chamadas de API, schemas e store. São doze módulos cobrindo a operação inteira.
O dashboard usa Recharts para receita, pedidos, desempenho de entregador e métodos de pagamento. Os primitivos de interface são shadcn/ui sobre Radix, com tokens Tailwind v4 e tema claro e escuro. A rota só carrega depois de um guard de autenticação (beforeLoad), e o Axios injeta o JWT em cada requisição e desloga no 401.
A decisão que mais rendeu no dia a dia foi montar uma camada de mocks com MSW. O backoffice inteiro sobe e exercita toda a interface sem nenhum backend rodando, o que deixou o front evoluir no próprio ritmo, desacoplado do andamento dos serviços. O aprendizado: um mock fiel ao contrato não é só conveniência de teste, é o que permite as duas pontas avançarem em paralelo.
O app do cliente
A terceira via é o app do cliente final, em Expo com React Native e expo-router, o mesmo roteamento file-based do backoffice, agora no mobile. A interface usa NativeWind, que é o Tailwind no React Native, então o vocabulário de estilo é o mesmo das outras pontas. O fluxo cobre o caminho do pedido: autenticação, navegação por categorias e busca com filtros, escolha de endereço no mapa com react-native-maps e expo-location, e o acompanhamento dos pedidos.
O estado é mais enxuto que o do painel: em vez de TanStack Query e stores, o app se apoia em contexto de React para autenticação e preferências, falando com o backend por uma única base de URL. Essa base única é o ponto da arquitetura: tanto o app quanto o backoffice enxergam um só endereço, o gateway, e não os sete serviços por trás. Mudar o roteamento interno do backend não mexe em nenhum cliente.
Onde está
O projeto está em construção, com as três vias erguidas. Os sete serviços rodam localmente via Docker Compose com Postgres, MongoDB e Kafka, com eventos e WebSocket funcionando; o backoffice web está completo sobre os mocks; e o app mobile cobre o fluxo principal do cliente. A peça que costura tudo para produção é o API gateway, a porta única que centraliza autenticação e roteamento. A forma, porém, já está decidida: cada serviço evolui e implanta no próprio ritmo, e cada cliente fala com um só endereço.