Claude Code et TypeScript : aller vite sans perdre la sécurité
Renforcez TypeScript avec Claude Code grâce à strict, Zod, Union, génériques, satisfies et tests de types.
Claude Code accélère fortement le développement TypeScript, surtout pour les formulaires, les helpers d’API et les tests. Mais si les frontières de types sont floues, il peut produire très vite du code fragile. Pour débuter sans se piéger, fixez les règles de types avant de demander la fonctionnalité.
Ici, strict signifie que TypeScript refuse davantage de code douteux.
Les types métier écrivent les règles produit sous forme de types.
Une Union discriminée modélise des états avec des formes différentes, et la validation runtime vérifie les données pendant l’exécution.
Donner d’abord une carte de types
Avant un long prompt, fournissez une petite carte : règles du compilateur, types métier, entrées externes, états et tests de types. Cette carte rend la sortie de Claude Code plus simple à relire.
flowchart TD
A["Besoin"] --> B["tsconfig: règles strict"]
B --> C["Types métier: Plan et Account"]
C --> D["Données externes: unknown puis validation"]
D --> E["État: Union discriminées"]
E --> F["Tests de types: expectTypeOf / tsd"]
F --> G["Implémentation et revue avec Claude Code"]
Les références officielles à garder ouvertes sont strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, Narrowing, Generics, Utility Types et la note sur satisfies.
Pour la validation runtime, ajoutez la documentation Zod.
À lire aussi : TypeScript Utility Types, TypeScript Generics et Zod Validation.
Commencer par un tsconfig strict
Ne dites pas seulement “fais-le en TypeScript”. Donnez d’abord le contrat du compilateur.
{
"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"]
}
Ajoutez ces contraintes au prompt.
Ce dépôt utilise TypeScript en mode strict.
N'introduis pas any. Les entrées externes doivent être unknown puis validées avec Zod.
Pour les Union dans les switch, ajoute un contrôle d'exhaustivité avec never.
Après l'implémentation, exécute npx tsc --noEmit.
noUncheckedIndexedAccess garde la possibilité de undefined quand on lit un tableau ou un record.
C’est strict, mais cela révèle tôt les champs API manquants, les listes vides et les traductions incomplètes.
Cas 1 : modéliser les offres SaaS
Les types métier traduisent les règles produit en TypeScript. Offres, permissions, factures et états de publication doivent être modélisés avant l’interface.
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 retire des membres d’une Union.
Partial rend les propriétés optionnelles : utile pour une mise à jour, risqué pour une création si on l’applique trop largement.
Cas 2 : valider les données API depuis unknown
Les types TypeScript disparaissent à l’exécution.
API, formulaires, cookies, localStorage, CSV et sorties d’IA peuvent être invalides.
Recevez-les en unknown, validez-les, puis utilisez le résultat typé.
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 signifie que la valeur n’est pas encore prouvée.
Contrairement à any, il impose une validation avant l’accès aux propriétés.
Cas 3 : fermer l’état de paiement avec une Union
Paiements, uploads, formulaires et jobs sont des machines à états.
Évitez status: string, trop large.
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;
}
}
}
La branche never oblige TypeScript à vérifier que tous les cas valides sont traités.
Si l’état refunded arrive plus tard, la branche manquante devient visible.
Cas 4 : génériques et satisfies
Les génériques rendent un helper réutilisable tout en gardant les types précis.
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));
Pour les objets de configuration, satisfies est souvent plus sûr qu’une assertion large.
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];
}
Ajouter des tests de types
Les types publics importants méritent des tests.
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;
}>();
});
Cas pratiques et pièges (Use case / Pitfall checklist)
| Cas d’usage | Ce que les types verrouillent | Ce que Claude Code génère |
|---|---|---|
| Facturation SaaS | offres, factures, permissions | branches UI, formulaires, messages |
| Admin API | schémas Zod, réponses | fetch, tableaux, chargement |
| CMS d’articles | slug, langue, publication, image | brouillons MDX, listes, corrections |
| Formulaire contact | schema d’entrée, Union résultat | UI, envoi, tests Vitest |
| Piège | Effet | Correction |
|---|---|---|
Réponse API en any | JSON invalide accepté | unknown et Zod |
status: string | états impossibles | Union discriminée |
Beaucoup de as User | erreurs masquées | schema, gardes, satisfies |
Partial<T> pour créer | champs requis devenus optionnels | séparer create et update |
| Pas de tests de types | API de types élargie | expectTypeOf ou tsd |
Dans les workflows ClaudeCodeLab, Masa a déjà vu des URL de locale invalides parce que lang était un simple string.
Une Union fermée pour les locales a rendu les corrections de Claude Code beaucoup plus prévisibles.
Règles et 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.
Pour un projet personnel, le catalogue de produits réunit des templates et checklists pour Claude Code.
Pour une équipe, la formation et le conseil Claude Code peuvent couvrir migration strict, frontières Zod, tests de types et règles CLAUDE.md dans un vrai dépôt.
Résultat testé
J’ai testé ce flux sur un petit projet TypeScript : remplacer les réponses API en any par unknown plus Zod, puis demander à Claude Code d’ajouter les branches Union et les tests expectTypeOf.
Le gain concret a été de trouver des états non traités et des accès à des propriétés inexistantes avant la revue.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Échelle de sécurité des permissions Claude Code
Passer du read-only aux éditions limitées, preuves et checks de déploiement sans perdre le contrôle.
Claude Code Small PR Proof Pack : rendre les petits changements reviewables
Un pack de preuve pour PR Claude Code : diff, vérifications, URL publique, CTA et rollback.
Gate de review avant commit avec Claude Code
Review avant commit avec Claude Code : diff, build, URL publique, liens Gumroad, CTA consultation, tests manquants et fichiers hors scope.