Retour au blog

Node.js 25 : TypeScript Natif Sans Transpilation Change Tout dans le Backend JavaScript

Salut HaWkers, l'un des changements les plus attendus par la communauté JavaScript est enfin arrivé de manière mature dans Node.js 25. Vous pouvez maintenant exécuter des fichiers TypeScript directement avec node index.ts sans avoir besoin de ts-node, Babel ou aucune étape de build.

Avez-vous déjà pensé à éliminer complètement l'étape de transpilation de votre workflow de développement ? Cette réalité est arrivée.

Ce Qui a Changé dans Node.js 25

Node.js 25, lancé comme version Current en novembre 2025, consolide le support natif de TypeScript qui a commencé de manière expérimentale dans les versions précédentes. La fonctionnalité est maintenant stable et prête pour l'utilisation en production.

Comment Ça Fonctionne

Node.js utilise une technique appelée type stripping - il supprime les types TypeScript au moment de l'exécution, sans faire aucune transformation de code. Cela signifie que votre code TypeScript s'exécute pratiquement à la même vitesse que du JavaScript pur.

# Avant - workflow traditionnel avec TypeScript
npx tsc src/index.ts --outDir dist
node dist/index.js

# Ou avec ts-node
npx ts-node src/index.ts

# Maintenant - Node.js 25+
node src/index.ts

Configuration Zéro

La beauté de cette fonctionnalité est qu'elle ne nécessite aucune configuration spéciale :

# Installation de Node.js 25
nvm install 25
nvm use 25

# Créez un fichier TypeScript
echo 'const message: string = "Hello, TypeScript!";
console.log(message);' > hello.ts

# Exécutez directement
node hello.ts
# Output: Hello, TypeScript!

Exemples Pratiques

API Express avec TypeScript

Voyez comme il est simple de créer une API Express avec typage complet :

// server.ts - exécutez avec : node server.ts
import express, { Request, Response, NextFunction } from 'express';

interface User {
  id: number;
  name: string;
  email: string;
  createdAt: Date;
}

interface ApiResponse<T> {
  success: boolean;
  data: T;
  message?: string;
}

const app = express();
app.use(express.json());

// Base de données simulée avec typage
const users: Map<number, User> = new Map();
let nextId = 1;

// Middleware de logging typé
const logRequest = (req: Request, res: Response, next: NextFunction): void => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
  next();
};

app.use(logRequest);

// GET /users - Liste tous les utilisateurs
app.get('/users', (req: Request, res: Response<ApiResponse<User[]>>) => {
  const allUsers = Array.from(users.values());

  res.json({
    success: true,
    data: allUsers
  });
});

// POST /users - Crée un nouvel utilisateur
app.post('/users', (req: Request, res: Response<ApiResponse<User>>) => {
  const { name, email } = req.body as { name: string; email: string };

  const newUser: User = {
    id: nextId++,
    name,
    email,
    createdAt: new Date()
  };

  users.set(newUser.id, newUser);

  res.status(201).json({
    success: true,
    data: newUser,
    message: 'User created successfully'
  });
});

// GET /users/:id - Recherche utilisateur par ID
app.get('/users/:id', (req: Request, res: Response<ApiResponse<User | null>>) => {
  const userId = parseInt(req.params.id);
  const user = users.get(userId);

  if (!user) {
    res.status(404).json({
      success: false,
      data: null,
      message: 'User not found'
    });
    return;
  }

  res.json({
    success: true,
    data: user
  });
});

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
  console.log(`Try: curl http://localhost:${PORT}/users`);
});

Scripts d'Automatisation Typés

Pour les scripts d'automatisation et les outils CLI, le TypeScript natif est parfait :

// deploy-script.ts - exécutez avec : node deploy-script.ts
import { execSync } from 'child_process';
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';

interface DeployConfig {
  environment: 'staging' | 'production';
  branch: string;
  buildCommand: string;
  deployTarget: string;
}

interface DeployResult {
  success: boolean;
  duration: number;
  commitHash: string;
  timestamp: Date;
}

function loadConfig(env: string): DeployConfig {
  const configPath = join(process.cwd(), `deploy.${env}.json`);

  if (!existsSync(configPath)) {
    throw new Error(`Config file not found: ${configPath}`);
  }

  const content = readFileSync(configPath, 'utf-8');
  return JSON.parse(content) as DeployConfig;
}

function executeCommand(command: string): string {
  console.log(`Executing: ${command}`);
  return execSync(command, { encoding: 'utf-8' });
}

function getCurrentCommit(): string {
  return executeCommand('git rev-parse HEAD').trim();
}

async function deploy(environment: string): Promise<DeployResult> {
  const startTime = Date.now();

  console.log(`\n🚀 Starting deployment to ${environment}\n`);

  const config = loadConfig(environment);
  const commitHash = getCurrentCommit();

  console.log(`📋 Configuration loaded:`);
  console.log(`   Environment: ${config.environment}`);
  console.log(`   Branch: ${config.branch}`);
  console.log(`   Commit: ${commitHash.substring(0, 8)}`);

  // Vérifier la branche
  const currentBranch = executeCommand('git branch --show-current').trim();
  if (currentBranch !== config.branch) {
    throw new Error(
      `Wrong branch! Expected ${config.branch}, got ${currentBranch}`
    );
  }

  // Build
  console.log(`\n🔨 Building...`);
  executeCommand(config.buildCommand);

  // Deploy
  console.log(`\n📦 Deploying to ${config.deployTarget}...`);
  executeCommand(`rsync -avz ./dist/ ${config.deployTarget}`);

  const duration = Date.now() - startTime;

  const result: DeployResult = {
    success: true,
    duration,
    commitHash,
    timestamp: new Date()
  };

  console.log(`\n✅ Deployment completed in ${duration}ms`);

  return result;
}

// Exécution
const env = process.argv[2] || 'staging';

deploy(env)
  .then(result => {
    console.log('\nDeploy Result:', result);
  })
  .catch(error => {
    console.error('\n❌ Deployment failed:', error.message);
    process.exit(1);
  });

Worker Threads avec TypeScript

Le support des Worker Threads fonctionne également parfaitement :

// main.ts
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
import { cpus } from 'os';

interface WorkerTask {
  id: number;
  data: number[];
  operation: 'sum' | 'average' | 'max' | 'min';
}

interface WorkerResult {
  id: number;
  result: number;
  processedBy: number;
}

if (isMainThread) {
  // Code principal
  const numCPUs = cpus().length;
  console.log(`Main thread running with ${numCPUs} CPU cores available`);

  const tasks: WorkerTask[] = [
    { id: 1, data: [1, 2, 3, 4, 5], operation: 'sum' },
    { id: 2, data: [10, 20, 30, 40, 50], operation: 'average' },
    { id: 3, data: [5, 15, 3, 22, 8], operation: 'max' },
    { id: 4, data: [100, 50, 75, 25, 200], operation: 'min' }
  ];

  const processTask = (task: WorkerTask): Promise<WorkerResult> => {
    return new Promise((resolve, reject) => {
      // Worker exécutant le même fichier .ts
      const worker = new Worker(new URL(import.meta.url), {
        workerData: task
      });

      worker.on('message', resolve);
      worker.on('error', reject);
    });
  };

  // Traite toutes les tâches en parallèle
  Promise.all(tasks.map(processTask))
    .then(results => {
      console.log('\nResults:');
      results.forEach(r => {
        console.log(`  Task ${r.id}: ${r.result} (Worker ${r.processedBy})`);
      });
    });

} else {
  // Code du Worker
  const task = workerData as WorkerTask;

  const operations = {
    sum: (arr: number[]) => arr.reduce((a, b) => a + b, 0),
    average: (arr: number[]) => arr.reduce((a, b) => a + b, 0) / arr.length,
    max: (arr: number[]) => Math.max(...arr),
    min: (arr: number[]) => Math.min(...arr)
  };

  const result: WorkerResult = {
    id: task.id,
    result: operations[task.operation](task.data),
    processedBy: process.pid
  };

  parentPort?.postMessage(result);
}

Autres Nouveautés de Node.js 25

En plus du TypeScript natif, Node.js 25 apporte d'autres améliorations significatives.

V8 Engine 13.6

La nouvelle version de V8 apporte des optimisations de performance :

// Améliorations des méthodes Array
const numbers: number[] = Array.from({ length: 1_000_000 }, (_, i) => i);

// Array.prototype.at() optimisé
const last = numbers.at(-1);  // Plus rapide que numbers[numbers.length - 1]

// Itérateurs optimisés
const doubled = numbers.values().map(n => n * 2).toArray();

// Object.groupBy() avec meilleure performance
interface Product {
  category: string;
  name: string;
  price: number;
}

const products: Product[] = [
  { category: 'electronics', name: 'Phone', price: 999 },
  { category: 'electronics', name: 'Laptop', price: 1499 },
  { category: 'clothing', name: 'Shirt', price: 49 }
];

const grouped = Object.groupBy(products, p => p.category);
// { electronics: [...], clothing: [...] }

Permission Model Amélioré

Le modèle de permissions est plus robuste :

# Exécuter avec des permissions restreintes
node --permission --allow-fs-read=/app/config --allow-fs-write=/app/logs server.ts

# Permettre uniquement un accès réseau spécifique
node --permission --allow-net=api.example.com:443 client.ts

npm 11 Intégré

npm 11 est inclus avec des améliorations :

# Workspaces plus rapides
npm install --workspaces

# Meilleure résolution des dépendances
npm install --prefer-dedupe

# Audit amélioré
npm audit --json

Limitations et Considérations

Bien que révolutionnaire, le TypeScript natif dans Node.js a certaines limitations importantes.

Ce Qui NE Fonctionne PAS

1. Les Enums ne sont pas supportés

// ❌ Ne fonctionne pas
enum Status {
  Active = 'active',
  Inactive = 'inactive'
}

// ✅ Utilisez des const objects ou des union types
const Status = {
  Active: 'active',
  Inactive: 'inactive'
} as const;

type StatusType = typeof Status[keyof typeof Status];

2. Decorators expérimentaux

// ❌ Les decorators ne fonctionnent pas nativement
@Injectable()
class UserService {}

// ✅ Utilisez des patterns alternatifs ou continuez avec la transpilation

3. Namespaces TypeScript

// ❌ Namespaces non supportés
namespace MyApp {
  export interface User {}
}

// ✅ Utilisez les ES Modules
// user.ts
export interface User {}

Quand Continuer à Utiliser une Étape de Build

Il existe des scénarios où la transpilation est toujours nécessaire :

Pour la Production avec Optimisation :

  • Minification du code
  • Tree-shaking
  • Bundle unique pour le déploiement

Pour les Fonctionnalités Avancées :

  • Decorators (NestJS, TypeORM)
  • Paths/aliases complexes
  • JSX/TSX (React)

Comparaison des Workflows

Aspect Build Traditionnel Node.js 25 Natif
Setup initial Complexe Zéro
Temps de dev Plus lent Instantané
Debug Source maps Direct
Hot reload Configuration Simple
Performance runtime Égale Égale
Features TS Toutes Majorité

Migration Graduelle

Si vous avez un projet existant, vous pouvez migrer graduellement :

// package.json
{
  "name": "my-project",
  "type": "module",
  "engines": {
    "node": ">=25.0.0"
  },
  "scripts": {
    "dev": "node --watch src/index.ts",
    "start": "node src/index.ts",
    "build": "tsc",
    "start:prod": "node dist/index.js"
  }
}
// tsconfig.json - configuration minimale
{
  "compilerOptions": {
    "target": "ES2024",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "skipLibCheck": true,
    "declaration": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}

Conclusion

Node.js 25 représente un jalon dans l'histoire du JavaScript côté serveur. La capacité d'exécuter TypeScript nativement élimine l'une des plus grandes frictions du développement moderne et rapproche l'expérience de langages comme Deno et Bun.

Pour les développeurs, cela signifie moins de configuration, un feedback plus rapide pendant le développement et une barrière d'entrée plus basse pour les nouveaux projets. C'est le type de changement qui simplifie notre vie sans sacrifier les avantages du typage statique.

Si vous n'avez pas encore essayé, je recommande de créer un projet de test et de sentir la différence. Pour approfondir TypeScript, consultez notre article sur State of JavaScript 2025 qui apporte des insights sur l'adoption de TypeScript dans l'écosystème.

C'est parti ! 🦅

Commentaires (0)

Cet article n'a pas encore de commentaires. Soyez le premier!

Ajouter des commentaires