Retour au blog

TypeScript-First : Pourquoi Tout Projet Node.js Commence avec TypeScript en 2025

Salut HaWkers, si vous créez encore de nouveaux projets Node.js avec JavaScript pur en 2025, je dois vous dire : vous nagez à contre-courant. TypeScript-first est devenu le standard absolu de l'industrie, et pour de bonnes raisons.

La question n'est plus "dois-je utiliser TypeScript ?" mais plutôt "pourquoi je n'utiliserais pas TypeScript ?". Explorons ce changement fondamental qui définit le développement moderne.

La Révolution TypeScript-First dans Node.js

En 2025, la grande majorité des nouveaux projets Node.js commence avec TypeScript. Ce n'est pas juste une tendance passagère - c'est un changement fondamental dans la façon dont nous développons des applications backend.

Les Chiffres Ne Mentent Pas

// Statistiques d'adoption en 2025
const typescriptAdoption = {
  newProjects: '87%', // Nouveaux projets utilisant TS
  migrations: '45%',  // Projets JS en migration
  jobRequirements: '92%', // Offres exigeant TS
  npmPackages: '76%' // Top packages avec types
};

// Les principaux frameworks sont déjà TypeScript-first
const frameworkSupport = {
  nestjs: 'TypeScript natif',
  fastify: 'Types first-class',
  trpc: 'TypeScript-only',
  prisma: 'TypeScript-first ORM',
  nextjs: 'TypeScript recommandé'
};

Pourquoi TypeScript a Gagné ?

1. Support Natif de Node.js

Node.js a maintenant un support expérimental pour exécuter TypeScript directement :

// Exécutez TypeScript directement sans compilation !
// node --experimental-strip-types server.ts

import express from 'express';
import type { Request, Response } from 'express';

const app = express();

interface User {
  id: string;
  name: string;
  email: string;
}

app.get('/api/users/:id', async (req: Request, res: Response) => {
  const userId = req.params.id;

  const user: User = await db.users.findUnique({
    where: { id: userId }
  });

  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }

  res.json(user);
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

2. Prévention des Bugs en Temps de Développement

TypeScript attrape des erreurs qui n'apparaîtraient qu'en production avec JavaScript :

// Exemple réel : API de paiement
interface PaymentRequest {
  amount: number;
  currency: 'USD' | 'EUR' | 'GBP';
  customerId: string;
  metadata?: Record<string, string>;
}

class PaymentService {
  async processPayment(request: PaymentRequest) {
    // TypeScript vous force à gérer tous les cas
    if (request.amount <= 0) {
      throw new Error('Amount must be positive');
    }

    // L'autocomplete vous montre uniquement les devises valides
    const exchangeRate = this.getExchangeRate(request.currency);

    return {
      transactionId: generateId(),
      amount: request.amount,
      currency: request.currency,
      status: 'processed' as const
    };
  }

  private getExchangeRate(currency: PaymentRequest['currency']): number {
    // Erreur de compilation si vous essayez de passer une devise invalide
    const rates = {
      USD: 1.0,
      EUR: 1.1,
      GBP: 1.3
    };

    return rates[currency];
  }
}

// Utilisation - TypeScript valide en temps de développement
const service = new PaymentService();

// ✅ Correct
await service.processPayment({
  amount: 100,
  currency: 'USD',
  customerId: 'cus_123'
});

// ❌ Erreur de compilation : "JPY" n'est pas une devise valide
await service.processPayment({
  amount: 100,
  currency: 'JPY', // TypeScript empêche ça !
  customerId: 'cus_123'
});

3. Refactoring Sûr et Fiable

// Changements sûrs dans du code volumineux
interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
  // Ajoute un nouveau champ obligatoire
  sku: string;
}

// TypeScript montre immédiatement tous les endroits qui
// doivent être mis à jour pour inclure SKU
function createProduct(data: Omit<Product, 'id'>): Product {
  return {
    id: generateId(),
    ...data
  };
}

// Erreur de compilation si SKU n'est pas passé
const product = createProduct({
  name: 'Laptop',
  price: 999,
  category: 'Electronics'
  // Missing 'sku' - TypeScript ne laisse pas passer !
});

Setup Moderne de TypeScript en 2025

Configuration Optimisée

// tsconfig.json - Configuration moderne et optimisée
{
  "compilerOptions": {
    "target": "ES2023",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2023"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Package.json Moderne

{
  "name": "modern-typescript-api",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/server.ts",
    "build": "tsc",
    "start": "node dist/server.js",
    "test": "vitest",
    "lint": "eslint src --ext .ts",
    "format": "prettier --write \"src/**/*.ts\""
  },
  "dependencies": {
    "fastify": "^4.25.0",
    "@fastify/cors": "^8.5.0",
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "@types/node": "^20.10.0",
    "tsx": "^4.7.0",
    "typescript": "^5.3.0",
    "vitest": "^1.0.0",
    "eslint": "^8.56.0",
    "@typescript-eslint/parser": "^6.17.0",
    "@typescript-eslint/eslint-plugin": "^6.17.0",
    "prettier": "^3.1.0"
  }
}

API Moderne avec Fastify + TypeScript

// src/server.ts
import Fastify from 'fastify';
import cors from '@fastify/cors';
import { z } from 'zod';

const server = Fastify({
  logger: true
});

await server.register(cors);

// Validation de schéma avec Zod
const CreateUserSchema = z.object({
  name: z.string().min(3),
  email: z.string().email(),
  age: z.number().int().positive()
});

type CreateUserInput = z.infer<typeof CreateUserSchema>;

// Route handlers typés
server.post<{ Body: CreateUserInput }>(
  '/users',
  async (request, reply) => {
    // Valide et parse automatiquement
    const userData = CreateUserSchema.parse(request.body);

    const user = await db.users.create({
      data: userData
    });

    return reply.code(201).send(user);
  }
);

server.get('/users/:id', async (request, reply) => {
  const { id } = request.params as { id: string };

  const user = await db.users.findUnique({
    where: { id }
  });

  if (!user) {
    return reply.code(404).send({ error: 'User not found' });
  }

  return user;
});

const start = async () => {
  try {
    await server.listen({ port: 3000 });
    console.log('Server listening on http://localhost:3000');
  } catch (err) {
    server.log.error(err);
    process.exit(1);
  }
};

start();

TypeScript avec Prisma : Type Safety End-to-End

// prisma/schema.prisma
model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String
  createdAt DateTime @default(now())
}

// src/repositories/user.repository.ts
import { PrismaClient, Prisma } from '@prisma/client';

const prisma = new PrismaClient();

export class UserRepository {
  // Prisma génère les types automatiquement !
  async findById(id: string) {
    return prisma.user.findUnique({
      where: { id },
      include: {
        posts: {
          where: { published: true }
        }
      }
    });
  }

  async create(data: Prisma.UserCreateInput) {
    return prisma.user.create({
      data,
      include: { posts: true }
    });
  }

  async findWithPosts(options: {
    skip?: number;
    take?: number;
  }) {
    return prisma.user.findMany({
      ...options,
      include: {
        posts: {
          orderBy: { createdAt: 'desc' }
        }
      }
    });
  }
}

// Types inférés automatiquement par Prisma !
const repo = new UserRepository();
const user = await repo.findById('123');
// user a le type complet : User & { posts: Post[] }

Tests Typés avec Vitest

// src/services/user.service.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { UserService } from './user.service';

describe('UserService', () => {
  let service: UserService;

  beforeEach(() => {
    service = new UserService();
  });

  it('doit créer un utilisateur avec des données valides', async () => {
    const userData = {
      name: 'John Doe',
      email: 'john@example.com',
      age: 30
    };

    const user = await service.createUser(userData);

    expect(user).toBeDefined();
    expect(user.id).toBeTruthy();
    expect(user.email).toBe(userData.email);
  });

  it('doit lever une erreur pour un email invalide', async () => {
    const userData = {
      name: 'John Doe',
      email: 'invalid-email',
      age: 30
    };

    await expect(
      service.createUser(userData)
    ).rejects.toThrow('Invalid email');
  });
});

Avantages Compétitifs sur le Marché

1. Code Plus Sûr et Maintenable

// Les changements se propagent automatiquement
type UserRole = 'admin' | 'user' | 'moderator';

function getUserPermissions(role: UserRole) {
  // Si vous ajoutez un nouveau rôle, TypeScript vous force à
  // mettre à jour tous les endroits qui utilisent les rôles
  switch (role) {
    case 'admin':
      return ['read', 'write', 'delete', 'admin'];
    case 'moderator':
      return ['read', 'write', 'moderate'];
    case 'user':
      return ['read'];
    // TypeScript garantit que tous les cas sont couverts
  }
}

2. Meilleure Expérience de Développement

  • Autocomplete intelligent - L'IDE sait exactement ce qui est disponible
  • Refactoring sûr - Renommez functions/variables sans crainte
  • Documentation inline - Les types sont de la documentation vivante
  • Détection instantanée des erreurs - Bugs attrapés en secondes, pas en production

3. Collaboration en Équipe

// Les autres développeurs comprennent votre code instantanément
interface CreateOrderDTO {
  userId: string;
  items: Array<{
    productId: string;
    quantity: number;
    price: number;
  }>;
  shippingAddress: {
    street: string;
    city: string;
    state: string;
    zipCode: string;
    country: string;
  };
  paymentMethod: 'credit_card' | 'debit_card' | 'paypal';
}

// Pas besoin de lire la documentation - les types expliquent tout
function createOrder(data: CreateOrderDTO): Promise<Order> {
  // Implémentation...
}

Le Futur est TypeScript-First

En 2025, TypeScript n'est pas optionnel - il est attendu. Les entreprises cherchent des développeurs maîtrisant TypeScript, les projets open source migrent vers TypeScript, et les nouveaux outils naissent TypeScript-first.

Si vous ne maîtrisez pas encore TypeScript, c'est le moment d'investir dans cette compétence. Ce n'est pas seulement ajouter des types au JavaScript - c'est penser différemment, construire des logiciels plus robustes et maintenables.

Pour mieux comprendre l'écosystème moderne de Node.js, je recommande : Serverless en 2025 : Comment Node.js Domine l'Architecture Sans Serveur où nous explorons comment TypeScript et Node.js ensemble dominent le serverless.

C'est parti !

Commentaires (0)

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

Ajouter des commentaires