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.tsConfiguration 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.tsnpm 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 --jsonLimitations 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 transpilation3. 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.

