Claude Code e TypeScript: dicas práticas para ganhar velocidade com segurança
Use strict, Zod, Union, genéricos, satisfies e testes de tipos para melhorar TypeScript com Claude Code.
Claude Code pode acelerar bastante o desenvolvimento TypeScript, principalmente em formulários, helpers de API e testes. Mas, se as fronteiras de tipo forem vagas, ele também acelera bugs. Para iniciantes, o hábito mais importante é definir as regras de tipo antes de pedir a funcionalidade.
Aqui, strict significa que TypeScript rejeita código suspeito.
Tipos de domínio são regras de negócio escritas como tipos.
Union discriminada modela estados com formatos diferentes, e validação em runtime confere os dados enquanto o programa roda.
Dê um mapa de tipos primeiro
Antes de um prompt longo, prepare um mapa pequeno: regras do compilador, tipos de domínio, entradas externas, estados e testes de tipo. Esse mapa facilita revisar o diff criado por Claude Code.
flowchart TD
A["Requisito"] --> B["tsconfig: regras strict"]
B --> C["Tipos de domínio: Plan e Account"]
C --> D["Dados externos: unknown e validação"]
D --> E["Estado: Union discriminadas"]
E --> F["Testes de tipo: expectTypeOf / tsd"]
F --> G["Implementação e revisão com Claude Code"]
Use a documentação oficial como base: strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, Narrowing, Generics, Utility Types e a nota sobre satisfies.
Para validação em runtime, veja também a documentação do Zod.
Leituras relacionadas: TypeScript Utility Types, TypeScript Generics e Zod Validation.
Comece com tsconfig strict
Não peça apenas “faça em TypeScript”. Fixe primeiro o contrato do compilador.
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts"]
}
Inclua as restrições no prompt.
Este repositório usa TypeScript strict.
Não introduza any. Trate entradas externas como unknown e valide com Zod.
Ao lidar com Union em switch, adicione checagem exaustiva com never.
Depois da implementação, rode npx tsc --noEmit.
noUncheckedIndexedAccess mantém undefined visível ao ler arrays e objetos.
É mais rigoroso, mas encontra campos ausentes de API, listas vazias e traduções incompletas mais cedo.
Caso 1: modelar planos SaaS
Tipos de domínio colocam regras de negócio no TypeScript. Planos, permissões, estado de cobrança e publicação devem vir antes da UI.
export type Plan = "free" | "pro" | "enterprise";
export type Account = {
id: string;
email: string;
plan: Plan;
seats: number;
trialEndsAt: string | null;
};
export type CreateAccountInput = {
email: string;
plan: Exclude<Plan, "enterprise">;
seats?: number;
};
export type UpdateAccountInput = Partial<
Pick<Account, "email" | "plan" | "seats" | "trialEndsAt">
>;
Exclude remove membros de uma Union.
Partial torna propriedades opcionais. É bom para updates, mas perigoso para create input se relaxar campos obrigatórios.
Caso 2: validar API a partir de unknown
Tipos TypeScript somem em runtime.
APIs, formulários, cookies, localStorage, CSV e saídas de IA podem vir quebrados.
Receba como unknown, valide e só depois use o valor tipado.
npm install zod
import { z } from "zod";
const AccountSchema = z.object({
id: z.string().min(1),
email: z.string().email(),
plan: z.enum(["free", "pro", "enterprise"]),
seats: z.number().int().positive(),
trialEndsAt: z.string().datetime().nullable()
});
type Account = z.infer<typeof AccountSchema>;
export function parseAccountResponse(json: unknown): Account {
return AccountSchema.parse(json);
}
unknown significa que o valor ainda não foi provado.
Diferente de any, ele força validação antes de acessar propriedades.
Caso 3: fechar estados de pagamento com Union
Pagamentos, uploads, formulários e jobs são máquinas de estado.
status: string é amplo demais.
type PaymentResult =
| { status: "pending"; invoiceId: string }
| { status: "paid"; invoiceId: string; paidAt: string }
| { status: "failed"; invoiceId: string; reason: string };
export function renderPaymentMessage(result: PaymentResult): string {
switch (result.status) {
case "pending":
return `Invoice ${result.invoiceId} is waiting for payment.`;
case "paid":
return `Invoice ${result.invoiceId} was paid at ${result.paidAt}.`;
case "failed":
return `Invoice ${result.invoiceId} failed: ${result.reason}.`;
default: {
const exhaustive: never = result;
return exhaustive;
}
}
}
O ramo never ajuda TypeScript a exigir todos os casos válidos.
Se refunded for adicionado depois, o compilador cobra o novo branch.
Caso 4: genéricos e satisfies
Genéricos criam helpers reutilizáveis sem perder o tipo específico da chamada.
export function groupBy<T, K extends PropertyKey>(
items: readonly T[],
getKey: (item: T) => K
): Partial<Record<K, T[]>> {
const grouped: Partial<Record<K, T[]>> = {};
for (const item of items) {
const key = getKey(item);
const bucket = grouped[key] ?? [];
bucket.push(item);
grouped[key] = bucket;
}
return grouped;
}
const accounts = [
{ id: "a1", plan: "free" },
{ id: "a2", plan: "pro" },
{ id: "a3", plan: "pro" }
] as const;
const byPlan = groupBy(accounts, (account) => account.plan);
const proAccounts = byPlan.pro ?? [];
console.log(proAccounts.map((account) => account.id));
Em objetos de configuração, prefira satisfies a uma asserção ampla.
type ApiRoute = {
method: "GET" | "POST" | "PATCH" | "DELETE";
path: `/${string}`;
auth: boolean;
};
const routes = {
listAccounts: { method: "GET", path: "/accounts", auth: true },
createAccount: { method: "POST", path: "/accounts", auth: true },
healthCheck: { method: "GET", path: "/health", auth: false }
} as const satisfies Record<string, ApiRoute>;
type RouteName = keyof typeof routes;
export function getRoute(name: RouteName) {
return routes[name];
}
Adicione testes de tipo
Tipos públicos importantes também precisam de teste.
npm install -D vitest tsd
import { expectTypeOf, test } from "vitest";
type CreateAccountInput = {
email: string;
plan: "free" | "pro";
seats?: number;
};
test("CreateAccountInput keeps the public API narrow", () => {
expectTypeOf<CreateAccountInput>().toMatchTypeOf<{
email: string;
plan: "free" | "pro";
seats?: number;
}>();
});
Usos reais e armadilhas (Use case / Pitfall checklist)
| Caso de uso | O que os tipos fixam | O que Claude Code gera |
|---|---|---|
| Cobrança SaaS | planos, faturas, permissões | UI, formulários, mensagens |
| Admin com API | schemas Zod, respostas | fetch, tabelas, loading |
| CMS de artigos | slug, idioma, publicação, imagem | rascunhos MDX, listagens, validação |
| Formulário contato | schema de entrada, Union de resultado | UI, envio, testes |
| Armadilha | Efeito | Correção |
|---|---|---|
Resposta API como any | JSON quebrado compila | unknown e Zod |
status: string | estados impossíveis | Union discriminada |
Muitos as User | erros escondidos | schema, guards, satisfies |
Partial<T> no create | campos obrigatórios viram opcionais | separar create e update |
| Sem testes de tipo | tipos públicos alargam | expectTypeOf ou tsd |
No fluxo de artigos da ClaudeCodeLab, Masa já viu URLs de locale inválido porque lang era apenas string.
Ao fechar o locale em uma Union, as sugestões de Claude Code ficaram bem mais previsíveis.
Regras e CTA
## TypeScript rules
- Use strict TypeScript.
- Do not introduce `any`. Use `unknown` at external boundaries.
- Prefer discriminated unions for states.
- Prefer `satisfies` over broad type assertions.
- Derive API types from Zod schemas when runtime data is involved.
- Add Vitest or tsd style type checks for exported helper types.
- Run `npx tsc --noEmit` before reporting completion.
Para projetos solo, o catálogo de produtos reúne templates e checklists para Claude Code.
Para equipes, o treinamento e consultoria Claude Code pode cobrir migração strict, fronteiras Zod, testes de tipo e regras de CLAUDE.md em um repositório real.
Resultado testado
Testei este fluxo em um projeto TypeScript pequeno: troquei respostas API de any para unknown com Zod e pedi ao Claude Code que adicionasse branches Union e testes expectTypeOf.
O ganho concreto foi encontrar estados não tratados e acessos a propriedades inexistentes antes da revisão.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Como pedir ao Claude Code para mexer em um único arquivo
Do desastre em que um 'deixa melhor' alterou 40 linhas nasceu um template de prompt que limita o escopo, valida e permite reverter.
Recuperar de negações de permissão no Claude Code sem enfraquecer guardrails
Transforme um comando negado em plano seguro com motivo, alternativa, provas e critérios de nova tentativa.
Claude Code Harness Smoke Test: prova de 15 minutos antes de confiar em um agente
Um smoke test para escopo, áreas bloqueadas, comandos de prova, URL pública e CTAs de receita no Claude Code.