Développement TypeScript & Node.js
Description
Guide de programmation TypeScript et Node.js — couvre la configuration stricte de TypeScript, les patterns asynchrones, la gestion d'erreurs avec Zod, les APIs Node.js, les tests avec Vitest, et les conventions de structure de projet.
Triggers
- typescript
- javascript
- node
- nodejs
- npm
- pnpm
- express
- zod
- vitest
- jest
- ts
- js
Instructions
1. Avant d'écrire du code
- Lire le
tsconfig.jsonet lepackage.jsondu projet pour comprendre la configuration et les dépendances. - Vérifier les patterns existants — conventions de nommage, structure des modules, style d'export.
- Pour les features non triviales, esquisser l'approche avant d'implémenter.
- Identifier les cas limites : inputs null/undefined, défaillances réseau, incompatibilités de type.
2. Style TypeScript
- Mode strict : toujours
"strict": truedans tsconfig constpar défaut,letseulement si mutation nécessaire, jamaisvarinterfacepour les formes d'objet,typepour les unions/intersections/types mappésasync/awaitplutôt que les Promises brutes ou callbacks- Named exports plutôt que default exports (sauf pages/layouts Next.js)
- Types de retour explicites sur les fonctions exportées
- Éviter
any— utiliserunknownet affiner avec des type guards
// tsconfig.json essentiels
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"noUncheckedIndexedAccess": true,
"skipLibCheck": true
}
}
3. Gestion d'erreurs
// Pattern Result avec erreurs typées
type Result<T> = { success: true; data: T } | { success: false; error: string };
async function fetchData(url: string): Promise<Result<Data>> {
try {
const response = await fetch(url);
if (!response.ok) {
return { success: false, error: `HTTP ${response.status}` };
}
const data = await response.json();
return { success: true, data };
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
return { success: false, error: message };
}
}
Validation d'input avec Zod
import { z } from "zod";
const UserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
type User = z.infer<typeof UserSchema>;
function createUser(input: unknown): User {
return UserSchema.parse(input); // lance ZodError si input invalide
}
// Ou parsing sécurisé (pas de throw)
const result = UserSchema.safeParse(input);
if (!result.success) {
console.error(result.error.flatten());
return;
}
const user = result.data; // entièrement typé
4. Patterns Node.js
// Utiliser le préfixe node: pour les modules built-in
import { readFile, writeFile, mkdir } from "node:fs/promises";
import { join, resolve } from "node:path";
import { existsSync } from "node:fs";
// Opérations de fichier asynchrones (ne jamais utiliser sync en production)
const content = await readFile(filePath, "utf-8");
await mkdir(dirPath, { recursive: true });
await writeFile(outputPath, data, "utf-8");
Timeouts avec AbortController
async function fetchWithTimeout(url: string, ms: number): Promise<Response> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), ms);
try {
return await fetch(url, { signal: controller.signal });
} finally {
clearTimeout(timeout);
}
}
Exécution de sous-processus
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
const { stdout, stderr } = await execFileAsync("git", ["status"], {
cwd: projectRoot,
timeout: 10_000,
});
Variables d'environnement
// Accès aux env vars type-safe
function requireEnv(key: string): string {
const value = process.env[key];
if (!value) throw new Error(`Missing env var: ${key}`);
return value;
}
const port = parseInt(process.env.PORT ?? "3000", 10);
5. Structure de projet
project/
src/
index.ts # point d'entrée
types.ts # définitions de types partagées
utils/ # fonctions utilitaires
services/ # logique métier
test/
*.test.ts # fichiers de test mirroir de src/
package.json
tsconfig.json
.env.example
Conventions :
- Un module par fichier, nommé d'après l'export principal
- Barrel exports (
index.ts) seulement aux limites de package, pas partout - Garder
types.tsséparé de l'implémentation - Co-localiser les tests à côté des sources ou dans un répertoire
test/parallèle
6. Tests
// Vitest (recommandé — rapide, ESM-natif, API compatible Jest)
import { describe, it, expect, vi } from "vitest";
describe("fetchData", () => {
it("returns data on success", async () => {
const result = await fetchData("https://api.example.com/data");
expect(result.success).toBe(true);
});
it("handles network errors", async () => {
const result = await fetchData("https://invalid.example.com");
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
it("mocks external dependencies", () => {
const mockFn = vi.fn().mockReturnValue(42);
expect(mockFn()).toBe(42);
expect(mockFn).toHaveBeenCalledOnce();
});
});
Exécuter : npx vitest (mode watch) ou npx vitest run (single pass)
7. Checklist de code review
- Types : Pas de
any, mode strict passe, types de retour sur les exports - Null safety :
noUncheckedIndexedAccessactivé, vérifications null avant accès - Gestion d'erreurs : Toutes les opérations async dans try-catch, erreurs typées non avalées
- Ressources : Streams/connexions fermées, AbortControllers nettoyés
- Sécurité : Input validé (Zod), pas d'eval(), pas d'input utilisateur non échappé en HTML
- Dépendances : Minimales, packages bien maintenus ; lockfile commité
- Tests : Coverage pour happy path et cas d'erreur
8. Pièges courants
- Oublier
await— une Promise non gérée échoue silencieusement - Utiliser
==au lieu de===— TypeScript attrape certains cas mais pas tous - Muter les arguments de fonction — toujours retourner de nouveaux objets/tableaux
- Imports circulaires — restructurer pour casser le cycle, ou utiliser des imports dynamiques
- Taille de bundle importante — vérifier avec
npx bundlesizeou un analyseur de build - I/O synchrone en contexte async — utiliser
fs/promises, pasfs.readFileSync
Verify
- Le code a été réellement exécuté (ou type-checked / linté selon le cas) et la sortie de commande est capturée
- Les dépendances et versions runtime utilisées sont pinées et enregistrées (ex. requirements.txt, package.json + lockfile, .nvmrc)
- Les erreurs ou avertissements émis par l'exécution sont adressés ou explicitement acceptés avec une raison
- Les nouveaux I/O externes (réseau, filesystem, DB) ont des timeouts et gestion d'erreurs, pas d'échec silencieux
- Les tests pour la modification ont été exécutés et le décompte pass/fail est dans la transcription
- Les secrets et credentials sont lus depuis env/secret store, pas en dur, et les fichiers
.envne sont pas commités
Notes
Pour les patterns spécifiques à Next.js/React, consulte les skills react-best-practices et composition-patterns à la place — ce skill couvre les patterns purs TypeScript et Node.js runtime.