typescript-nodejs

Par elophanto · elophanto

npx skills add https://github.com/elophanto/elophanto --skill typescript-nodejs

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

  1. Lire le tsconfig.json et le package.json du projet pour comprendre la configuration et les dépendances.
  2. Vérifier les patterns existants — conventions de nommage, structure des modules, style d'export.
  3. Pour les features non triviales, esquisser l'approche avant d'implémenter.
  4. Identifier les cas limites : inputs null/undefined, défaillances réseau, incompatibilités de type.

2. Style TypeScript

  • Mode strict : toujours "strict": true dans tsconfig
  • const par défaut, let seulement si mutation nécessaire, jamais var
  • interface pour les formes d'objet, type pour les unions/intersections/types mappés
  • async/await plutô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 — utiliser unknown et 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.ts sé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 : noUncheckedIndexedAccess activé, 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 bundlesize ou un analyseur de build
  • I/O synchrone en contexte async — utiliser fs/promises, pas fs.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 .env ne 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.

Skills similaires