É fácil acabar com um frontend totalmente tipado, um backend totalmente tipado, e uma API entre os dois que nenhum dos dois sistemas de fato verifica — nesse momento, "type safety" é uma propriedade local de cada lado, não uma propriedade do sistema. A fronteira é onde a corretude sobrevive ou silenciosamente para de importar.
Onde a garantia realmente quebra
Um cliente tipado chamando um servidor tipado geralmente parece seguro: o cliente tem uma interface descrevendo o formato da resposta, o servidor tem um tipo descrevendo o que retorna, tudo compila limpo. Nada disso significa que as duas definições concordam entre si. São duas afirmações independentes sobre o mesmo formato de dados na rede, mantidas manualmente (ou geradas a partir de uma especificação que ela mesma desatualizou), sem nada garantindo que continuem sincronizadas conforme a API evolui. O servidor pode começar a retornar um campo como string | null em vez de string, subir pra produção limpo porque os próprios tipos do servidor foram atualizados, e o cliente silenciosamente continua assumindo string — um erro de tipo que nenhum compilador dos dois lados jamais vai pegar, porque o compilador de nenhum dos lados enxerga o código do outro.
De onde vem a segurança real de ponta a ponta
Os projetos que realmente acertam isso compartilham uma característica: uma única fonte de verdade pro contrato, com os dois lados derivando dela em vez de cada lado manter uma cópia independente e sincronizada manualmente. Na prática, isso aparece de algumas formas:
- Ferramentas schema-first (OpenAPI/JSON Schema com geração de código) que geram os tipos do cliente e do servidor a partir de uma única definição, de forma que desalinhamento vira falha de build em vez de surpresa em produção.
- Frameworks de RPC que compartilham tipos TypeScript diretamente através da fronteira num monorepo, de forma que o cliente importa o tipo real do servidor em vez de uma aproximação escrita à mão.
- Validação em tempo de execução na fronteira (fazer parse de toda requisição e resposta contra um schema, não só um cast) — porque mesmo um tipo gerado só descreve o que a API deveria retornar, não o que ela de fato retornou numa resposta específica, e uma fronteira de rede é exatamente onde "deveria" e "de fato fez" divergem.
A parte que é fácil de pular
Esse último ponto importa mais do que costuma receber crédito. Um tipo TypeScript é apagado em tempo de compilação — ele não força nada em tempo de execução. Uma Response tipada como User que na verdade contém um JSON malformado, um campo faltando, ou um payload de erro de um serviço upstream vai fluir tranquilamente pelo resto do app como se fosse um User válido, porque a anotação de tipo nunca foi de fato checada contra os bytes reais que chegaram pela rede. Segurança real de ponta a ponta precisa de uma checagem em tempo de execução na costura, não só uma promessa em tempo de compilação de cada lado dela.
A lição
"Usamos TypeScript no frontend e no backend" descreve duas ilhas tipadas, não um sistema type-safe único. Os tipos só viram uma garantia em nível de sistema quando algo força os dois lados a concordarem entre si — na fronteira, nem antes nem depois dela.
Vamos conversar?
Tem um projeto, uma vaga ou só quer trocar uma ideia sobre o assunto? Envie uma mensagem.
Entrar em contato