TypeScript além do básico: Padrões que uso em produção
TypeScript além do básico: Padrões que uso em produção
TypeScript virou o padrão no ecossistema JavaScript por um motivo: ele pega bugs na hora da digitação, não em produção. Mas depois de 10 anos escrevendo TypeScript em empresas como Chipper Cash (fintech com milhões de usuários) e ThoughtWorks, percebi que a maioria dos devs usa só 20% do que a linguagem oferece.
Este artigo não é sobre tipos básicos. É sobre os padrões que realmente fazem diferença quando seu projeto cresce.
1. Discriminated Unions — Seu melhor amigo
Em vez de usar optional fields e tratar undefined em todo lugar, use discriminated unions:
// ❌ Ruim
type ApiResponse = {
success: boolean;
data?: User[];
error?: string;
};
// ✅ Bom
type ApiResponse =
| { status: 'success'; data: User[] }
| { status: 'error'; code: number; message: string }
| { status: 'loading' };
// O TypeScript estreita o tipo automaticamente
function handleResponse(res: ApiResponse) {
switch (res.status) {
case 'success':
return renderUsers(res.data); // res.data é User[]
case 'error':
return showError(res.message); // res.message é string
case 'loading':
return showSpinner();
}
}
Isso elimina if (data && error) e estados impossíveis de representar. Usei esse padrão extensivamente na Chipper Cash para modelar respostas de APIs financeiras — cada estado possível estava explícito no tipo.
2. Branded Types — Domínio no tipo
CPF, ID, Email, Slug são todos strings — até não serem. Um string não te impede de passar um email onde espera um CPF:
// ❌ Ruim
function getUser(id: string) {}
function getOrder(id: string) {}
getUser(getOrder('123')); // Compila, mas não faz sentido
// ✅ Bom
type Brand<T, B> = T & { __brand: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
function getUser(id: UserId) {}
function getOrder(id: OrderId) {}
getUser('abc' as UserId); // OK
getUser(getOrder('abc' as OrderId)); // ❌ Erro de tipo!
Na ThoughtWorks, usávamos isso pra modelar IDs de agregados em sistemas de banking — o compilador impedia misturar IDs de domínios diferentes.
3. Tratamento de erros sem try/catch
Try/catch aninhado é o maior gerador de código feio. Uma alternativa que funciona bem em produção é usar um tipo Result:
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
async function divide(a: number, b: number): Promise<Result<number>> {
if (b === 0) return { ok: false, error: new Error('Division by zero') };
return { ok: true, value: a / b };
}
const result = await divide(10, 0);
if (!result.ok) {
console.error(result.error.message);
return;
}
console.log(result.value); // Tipo estreitado para number
Em projetos com múltiplas chamadas encadeadas (típico em integrações com APIs de terceiros), isso elimina o aninhamento de try/catch.
4. Type Guards customizados
Nem tudo dá pra expressar com tipos estáticos. Pra validação em runtime, use type guards:
interface User {
id: string;
email: string;
role: 'admin' | 'user';
}
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
typeof (obj as any).id === 'string' &&
typeof (obj as any).email === 'string' &&
['admin', 'user'].includes((obj as any).role)
);
}
// Uso: o TypeScript sabe que data é User após o guard
fetch('/api/user')
.then(res => res.json())
.then((data: unknown) => {
if (isUser(data)) {
console.log(data.email); // ✅ type-safe
}
});
5. Estrutura de projetos que escalam
Depois de anos ajustando arquiteturas, cheguei numa estrutura que funciona do time pequeno ao grande:
src/
shared/ # Tipos, utils, constantes (SEM dependências internas)
types/
utils/
domain/ # Lógica de negócio (regras PURAS, sem efeito colateral)
user/
order/
services/ # Integrações externas (APIs, bancos, filas)
http/ # Controllers, middlewares, rotas
routes/
middlewares/
infra/ # Database, cache, config
Regra de ouro: shared não importa domain, domain não importa infra. Só services e http fazem a ponte. Isso te permite testar domain com mocking zero.
Sobre a Haruo: Haruo é uma software house full-stack especializada em produtos web, APIs e automação com IA. haruo.dev
Quer elevar a qualidade do código TypeScript da sua equipe? Fale com a Haruo →
Quer levar isso para produção?
Na Haruo, implementamos agents de IA, automações e sistemas escaláveis para empresas que querem resultados reais. Vamos conversar sobre seu projeto.
Falar com a Haruo →