Noqta
  • Accueil
  • Services
  • À propos
  • Écrits
  • Se connecter
écrits/tutorial/2026/05
● Tutorial12 mai 2026·30 min

Construire des Workflows Durables avec Temporal.io et TypeScript

Maîtrisez Temporal.io pour créer des workflows fault-tolerant en TypeScript. Apprenez les workers, activités, signaux, requêtes et patterns de déploiement en production.

AI Bot
AI Bot
Author
·EN · FR · AR

Les systèmes distribués sont complexes. Les processus plantent, les réseaux tombent, et les services s'arrêtent au pire moment possible. Les files de tâches traditionnelles gèrent des opérations simples, mais que se passe-t-il quand vous avez besoin d'un workflow qui s'étend sur plusieurs heures, implique plusieurs services, et doit survivre aux redémarrages de serveur ?

Temporal.io résout ce problème en fournissant une plateforme d'exécution durable où votre code s'exécute comme si les pannes n'existaient tout simplement pas. Vos workflows persistent malgré les crashes, les tentatives se font automatiquement, et vous bénéficiez d'une visibilité complète sur chaque étape d'exécution.

Dans ce tutoriel, vous allez construire un système de traitement de commandes prêt pour la production avec Temporal.io et TypeScript. À la fin, vous saurez modéliser des processus métier complexes sous forme de workflows fiables et observables.

Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Node.js 20 ou version ultérieure
  • Une expérience intermédiaire en TypeScript
  • Une bonne compréhension de async/await et des Promises
  • Docker installé (optionnel — le CLI Temporal gère le développement local)
  • Une familiarité avec les API REST

Ce que vous allez construire

Vous allez créer un système de traitement de commandes avec ces étapes :

  1. Valider la commande et vérifier l'inventaire
  2. Débiter le moyen de paiement du client
  3. Envoyer un e-mail de confirmation
  4. Planifier la préparation et l'expédition
  5. Gérer les erreurs à chaque étape avec des tentatives automatiques

Ce workflow sera entièrement fault-tolerant — si votre serveur plante en cours de traitement, Temporal reprend exactement là où il s'est arrêté au redémarrage.

Étape 1 : Démarrer le serveur Temporal en développement

La manière la plus simple de faire tourner Temporal en local est le CLI Temporal. Installez-le via Homebrew sur macOS :

brew install temporal

Ou téléchargez-le depuis la page des releases officielles pour Linux et Windows.

Démarrez le serveur de développement :

temporal server start-dev

Cela lance :

  • Temporal Server sur le port 7233
  • Interface Web sur http://localhost:8233

Ouvrez l'interface web dans votre navigateur pour surveiller les exécutions de workflows en temps réel. Gardez ce terminal ouvert tout au long du tutoriel.

Étape 2 : Initialiser le projet TypeScript

Créez un nouveau projet Node.js :

mkdir temporal-order-processing
cd temporal-order-processing
npm init -y

Installez le SDK Temporal et les dépendances TypeScript :

npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity
npm install -D typescript ts-node @types/node

Initialisez TypeScript :

npx tsc --init

Mettez à jour tsconfig.json :

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "skipLibCheck": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Créez la structure du projet :

mkdir -p src/{workflows,activities,workers,client,shared}

Votre arborescence finale ressemblera à :

temporal-order-processing/
├── src/
│   ├── shared/
│   │   └── types.ts
│   ├── workflows/
│   │   └── order-workflow.ts
│   ├── activities/
│   │   └── order-activities.ts
│   ├── workers/
│   │   └── worker.ts
│   └── client/
│       └── start-workflow.ts
├── tsconfig.json
└── package.json

Étape 3 : Comprendre les concepts fondamentaux de Temporal

Avant d'écrire du code, comprenons les quatre composants clés :

Workflows

Un Workflow est une fonction durable qui orchestre votre logique métier. Les workflows sont déterministes — avec les mêmes entrées, ils produisent toujours la même séquence d'opérations. Ce déterminisme permet à Temporal de rejouer et de récupérer les workflows après une panne.

Contraintes fondamentales pour les workflows :

  • Pas d'I/O direct (pas d'appels base de données, pas de requêtes HTTP, pas d'accès au système de fichiers)
  • Pas d'utilisation directe de Date.now() ou Math.random()
  • Tous les effets de bord passent par des Activities

Activities

Une Activity est l'endroit où réside votre logique métier réelle — requêtes base de données, appels API, envoi d'e-mails. Les activités peuvent échouer et seront relancées selon votre politique de retry configurée.

Workers

Un Worker est un processus qui héberge vos workflows et activités. Il interroge le serveur Temporal pour des tâches et les exécute. Vous pouvez lancer plusieurs workers pour la mise à l'échelle horizontale.

Le client Temporal

Le Client est utilisé pour démarrer des workflows et envoyer des signaux aux workflows en cours d'exécution.

Étape 4 : Définir les types partagés

// src/shared/types.ts
export interface OrderItem {
  productId: string;
  quantity: number;
  price: number;
}
 
export interface OrderInput {
  orderId: string;
  customerId: string;
  items: OrderItem[];
  totalAmount: number;
  paymentMethodId: string;
}
 
export interface OrderResult {
  orderId: string;
  status: "completed" | "failed" | "cancelled";
  chargeId?: string;
  trackingNumber?: string;
  message: string;
}

Étape 5 : Écrire les activities

Les activities contiennent la logique métier réelle :

// src/activities/order-activities.ts
import { OrderInput } from "../shared/types";
 
export async function validateOrder(input: OrderInput): Promise<boolean> {
  console.log(`Validation de la commande ${input.orderId}`);
  await new Promise((resolve) => setTimeout(resolve, 100));
 
  if (input.items.length === 0) {
    throw new Error("La commande doit contenir au moins un article");
  }
  if (input.totalAmount <= 0) {
    throw new Error("Le montant total doit être supérieur à zéro");
  }
 
  return true;
}
 
export async function chargePayment(
  orderId: string,
  amount: number,
  paymentMethodId: string
): Promise<string> {
  console.log(`Débit de ${amount} pour la commande ${orderId}`);
  await new Promise((resolve) => setTimeout(resolve, 500));
 
  const chargeId = `ch_${Date.now()}_${orderId}`;
  return chargeId;
}
 
export async function sendConfirmationEmail(
  orderId: string,
  customerId: string,
  chargeId: string
): Promise<void> {
  console.log(`Envoi du mail de confirmation pour la commande ${orderId}`);
  await new Promise((resolve) => setTimeout(resolve, 200));
}
 
export async function scheduleShipping(
  orderId: string,
  itemCount: number
): Promise<string> {
  console.log(`Planification de l'expédition pour la commande ${orderId}`);
  await new Promise((resolve) => setTimeout(resolve, 300));
 
  return `TRK${Date.now()}`;
}

Les activities peuvent effectuer tout appel I/O — requêtes base de données, requêtes HTTP, accès au système de fichiers. Ce sont les seuls endroits où des effets de bord doivent se produire dans un workflow Temporal.

Étape 6 : Écrire le workflow de commande

Créez le workflow qui orchestre ces activities en séquence :

// src/workflows/order-workflow.ts
import {
  proxyActivities,
  defineSignal,
  defineQuery,
  setHandler,
} from "@temporalio/workflow";
import type * as activities from "../activities/order-activities";
import type { OrderInput, OrderResult } from "../shared/types";
 
const {
  validateOrder,
  chargePayment,
  sendConfirmationEmail,
  scheduleShipping,
} = proxyActivities<typeof activities>({
  startToCloseTimeout: "30 seconds",
  retry: {
    maximumAttempts: 3,
    initialInterval: "1 second",
    maximumInterval: "10 seconds",
    backoffCoefficient: 2,
  },
});
 
export const cancelOrderSignal = defineSignal<[string]>("cancelOrder");
export const getOrderStatusQuery = defineQuery<string>("getOrderStatus");
 
export async function orderWorkflow(input: OrderInput): Promise<OrderResult> {
  let cancelled = false;
  let cancellationReason = "";
  let currentStatus = "pending";
 
  setHandler(cancelOrderSignal, (reason: string) => {
    cancelled = true;
    cancellationReason = reason;
  });
 
  setHandler(getOrderStatusQuery, () => currentStatus);
 
  try {
    currentStatus = "validating";
    await validateOrder(input);
 
    if (cancelled) {
      return {
        orderId: input.orderId,
        status: "cancelled",
        message: `Commande annulée avant paiement : ${cancellationReason}`,
      };
    }
 
    currentStatus = "charging";
    const chargeId = await chargePayment(
      input.orderId,
      input.totalAmount,
      input.paymentMethodId
    );
 
    if (cancelled) {
      return {
        orderId: input.orderId,
        status: "cancelled",
        chargeId,
        message: "Commande annulée après paiement. Remboursement initié.",
      };
    }
 
    currentStatus = "confirming";
    await sendConfirmationEmail(input.orderId, input.customerId, chargeId);
 
    currentStatus = "shipping";
    const trackingNumber = await scheduleShipping(
      input.orderId,
      input.items.length
    );
 
    currentStatus = "completed";
 
    return {
      orderId: input.orderId,
      status: "completed",
      chargeId,
      trackingNumber,
      message: "Commande traitée avec succès",
    };
  } catch (error) {
    currentStatus = "failed";
    return {
      orderId: input.orderId,
      status: "failed",
      message: error instanceof Error ? error.message : "Erreur inconnue",
    };
  }
}

N'importez jamais les modules Node.js natifs comme fs, http ou crypto directement dans les fichiers de workflow. Le sandbox Temporal bloque ces imports. Utilisez import type pour les imports depuis les fichiers d'activities.

Étape 7 : Configurer le Worker

Le processus worker fait le lien entre le serveur Temporal et votre code applicatif :

// src/workers/worker.ts
import { Worker, NativeConnection } from "@temporalio/worker";
import * as activities from "../activities/order-activities";
import path from "path";
 
async function run() {
  const connection = await NativeConnection.connect({
    address: "localhost:7233",
  });
 
  const worker = await Worker.create({
    connection,
    namespace: "default",
    taskQueue: "order-processing",
    workflowsPath: path.join(__dirname, "../workflows"),
    activities,
  });
 
  console.log("Worker démarré — en attente de tâches sur : order-processing");
  await worker.run();
}
 
run().catch((err) => {
  console.error("Échec du démarrage du worker :", err);
  process.exit(1);
});

Étape 8 : Créer le client de workflow

Le client déclenche les exécutions de workflow et peut les interroger :

// src/client/start-workflow.ts
import { Client, Connection } from "@temporalio/client";
import {
  orderWorkflow,
  getOrderStatusQuery,
} from "../workflows/order-workflow";
import { OrderInput } from "../shared/types";
 
async function main() {
  const connection = await Connection.connect({ address: "localhost:7233" });
  const client = new Client({ connection, namespace: "default" });
 
  const orderInput: OrderInput = {
    orderId: "ORDER-001",
    customerId: "CUST-123",
    items: [
      { productId: "PROD-A", quantity: 2, price: 29.99 },
      { productId: "PROD-B", quantity: 1, price: 49.99 },
    ],
    totalAmount: 109.97,
    paymentMethodId: "pm_card_visa",
  };
 
  const handle = await client.workflow.start(orderWorkflow, {
    taskQueue: "order-processing",
    workflowId: `order-${orderInput.orderId}`,
    args: [orderInput],
  });
 
  console.log(`Workflow démarré : ${handle.workflowId}`);
 
  // Interroger le statut jusqu'à la fin
  let done = false;
  while (!done) {
    const status = await handle.query(getOrderStatusQuery);
    console.log(`Statut actuel : ${status}`);
    if (status === "completed" || status === "failed") done = true;
    await new Promise((r) => setTimeout(r, 1000));
  }
 
  const result = await handle.result();
  console.log("Résultat final :", result);
 
  await connection.close();
}
 
main().catch(console.error);

Étape 9 : Scripts npm et lancement du système

Mettez à jour package.json :

{
  "scripts": {
    "worker": "ts-node src/workers/worker.ts",
    "start": "ts-node src/client/start-workflow.ts"
  }
}

Ouvrez trois fenêtres de terminal :

Terminal 1 — Serveur Temporal :

temporal server start-dev

Terminal 2 — Processus Worker :

npm run worker

Terminal 3 — Déclenchement du Workflow :

npm run start

Vous verrez les logs de chaque activity dans le terminal du worker et les mises à jour de statut dans le terminal client. Ouvrez http://localhost:8233 pour inspecter l'historique complet des événements en temps réel.

Étape 10 : Les politiques de retry expliquées

La configuration des retries est critique pour la production :

proxyActivities({
  startToCloseTimeout: "30 seconds",   // Durée max par tentative
  scheduleToCloseTimeout: "5 minutes", // Durée max totale, retries inclus
  retry: {
    maximumAttempts: 3,
    initialInterval: "1 second",       // Attendre 1s avant le premier retry
    maximumInterval: "30 seconds",     // Ne jamais attendre plus de 30s
    backoffCoefficient: 2,             // Exponentiel : 1s, 2s, 4s...
    nonRetryableErrorTypes: ["PaymentDeclinedError"],
  },
})

Pour les activités de paiement, utilisez maximumAttempts: 1 pour éviter un double débit. Pour l'envoi d'e-mails, autorisez jusqu'à 5 tentatives.

Étape 11 : Workflows longue durée avec sleep

La fonction sleep de Temporal met en pause un workflow pour n'importe quelle durée — des secondes aux mois — sans consommer de ressources :

import { sleep } from "@temporalio/workflow";
 
export async function subscriptionRenewalWorkflow(userId: string) {
  // Attendre 30 jours avant le renouvellement
  await sleep("30 days");
 
  await chargeRenewalFee(userId);
  await sendRenewalConfirmation(userId);
 
  // Planifier le prochain cycle
  await sleep("30 days");
}

C'est l'une des fonctionnalités les plus puissantes de Temporal — remplacez vos cron jobs par du code lisible qui gère automatiquement les retries et l'état.

Étape 12 : Tests avec TestWorkflowEnvironment

Temporal fournit un environnement de test pour exécuter des workflows sans serveur réel :

// src/__tests__/order-workflow.test.ts
import { TestWorkflowEnvironment } from "@temporalio/testing";
import { Worker } from "@temporalio/worker";
import { orderWorkflow } from "../workflows/order-workflow";
import * as activities from "../activities/order-activities";
 
describe("Workflow de commande", () => {
  let testEnv: TestWorkflowEnvironment;
 
  beforeAll(async () => {
    testEnv = await TestWorkflowEnvironment.createLocal();
  });
 
  afterAll(async () => {
    await testEnv.teardown();
  });
 
  it("se termine avec succès pour une commande valide", async () => {
    const { client, nativeConnection } = testEnv;
 
    const worker = await Worker.create({
      connection: nativeConnection,
      namespace: "default",
      taskQueue: "test-queue",
      workflowsPath: require.resolve("../workflows/order-workflow"),
      activities,
    });
 
    const result = await worker.runUntil(
      client.workflow.execute(orderWorkflow, {
        taskQueue: "test-queue",
        workflowId: "test-001",
        args: [{
          orderId: "TEST-001",
          customerId: "CUST-001",
          items: [{ productId: "P1", quantity: 1, price: 10 }],
          totalAmount: 10,
          paymentMethodId: "pm_test",
        }],
      })
    );
 
    expect(result.status).toBe("completed");
    expect(result.chargeId).toBeDefined();
    expect(result.trackingNumber).toBeDefined();
  });
});

Installez le package de test :

npm install -D @temporalio/testing

Étape 13 : Intégration avec une API Express

Connectez Temporal à votre couche HTTP :

// src/api/server.ts
import express from "express";
import { Client, Connection } from "@temporalio/client";
import { orderWorkflow, getOrderStatusQuery } from "../workflows/order-workflow";
import type { OrderInput } from "../shared/types";
 
const app = express();
app.use(express.json());
 
let temporalClient: Client;
 
async function initClient() {
  const connection = await Connection.connect({ address: "localhost:7233" });
  temporalClient = new Client({ connection, namespace: "default" });
}
 
// POST /orders — démarre le workflow
app.post("/orders", async (req, res) => {
  try {
    const input: OrderInput = req.body;
    const handle = await temporalClient.workflow.start(orderWorkflow, {
      taskQueue: "order-processing",
      workflowId: `order-${input.orderId}`,
      args: [input],
    });
    res.json({ workflowId: handle.workflowId });
  } catch {
    res.status(500).json({ error: "Impossible de démarrer le workflow" });
  }
});
 
// GET /orders/:id/status — interroge le workflow en cours
app.get("/orders/:id/status", async (req, res) => {
  try {
    const handle = temporalClient.workflow.getHandle(`order-${req.params.id}`);
    const status = await handle.query(getOrderStatusQuery);
    res.json({ orderId: req.params.id, status });
  } catch {
    res.status(404).json({ error: "Workflow de commande introuvable" });
  }
});
 
initClient().then(() => {
  app.listen(3001, () => console.log("API démarrée sur le port 3001"));
});

Résolution des problèmes courants

"Connection refused" au démarrage du worker : Vérifiez que temporal server start-dev tourne dans un terminal séparé et que le port 7233 est disponible.

Workflow bloqué à l'état "Running" indéfiniment : Vérifiez que le processus worker tourne et qu'il est connecté au même nom de task queue que celui utilisé dans le client. Inspectez l'historique des événements dans l'interface Web pour les détails d'erreur.

Les activities ne font pas de retry après un échec : Confirmez que la politique de retry est configurée sur proxyActivities et non à l'intérieur du corps de la fonction workflow.

Erreurs TypeScript dans les fichiers de workflow : Les workflows s'exécutent dans un environnement sandbox. Évitez les modules Node.js natifs et utilisez import type pour les imports depuis les fichiers d'activities.

Prochaines étapes

Avec ce setup fonctionnel, explorez ces patterns avancés :

  • Child Workflows : Décomposer les workflows complexes en sous-workflows réutilisables
  • Schedules : Remplacer les cron jobs par l'API de planification intégrée de Temporal
  • Pattern Saga : Implémenter des transactions distribuées avec des activities compensatoires
  • API de versioning : Mettre à jour les workflows en cours d'exécution de façon sécurisée avec patched()
  • Temporal Cloud : Temporal entièrement géré pour la production

Conclusion

Vous avez construit un système de traitement de commandes prêt pour la production avec Temporal.io et TypeScript. Votre workflow survit désormais aux pannes de serveur, relance automatiquement les activities échouées, et expose le statut en temps réel via les queries.

La vraie puissance de Temporal est qu'il élimine toute une classe de problèmes liés aux systèmes distribués : vous n'avez plus besoin de construire manuellement la logique de retry, gérer l'état des files de tâches, ou implémenter des gestionnaires de transactions compensatoires. Que vous construisiez des flux de paiement, des pipelines d'onboarding, des jobs ETL, ou des processus d'approbation multi-étapes, Temporal donne à votre logique métier la durabilité qu'exige la production.

● Tags
#temporal#typescript#workflows#distributed-systems#nodejs#intermediate#30 min de lecture
● Partage
● Une question ?

Discutez de cet article avec un agent Noqta.

AI Bot
AI Bot
Author · noqta
Suivre ↗

● À lire ensuite

Construire un Agent IA Autonome avec Agentic RAG et Next.js
● Tutorial

Construire un Agent IA Autonome avec Agentic RAG et Next.js

11 févr. 2026
Construire des agents IA from scratch avec TypeScript : maîtriser le pattern ReAct avec le Vercel AI SDK
● Tutorial

Construire des agents IA from scratch avec TypeScript : maîtriser le pattern ReAct avec le Vercel AI SDK

12 févr. 2026
Construire un moteur de recherche sémantique avec Next.js 15, OpenAI et Pinecone
● Tutorial

Construire un moteur de recherche sémantique avec Next.js 15, OpenAI et Pinecone

21 mars 2026
Noqta
Conditions générales · Politique de Confidentialité
Services
  • Automatisation IA
  • Agents IA
  • Automatisation CX
  • Vibe Coding
  • Gestion de Projet
  • Assurance Qualité
  • Développement Web
  • Intégration API
  • Applications Métier
  • Maintenance
  • Low-Code/No-Code
Liens
  • À propos de nous
  • Comment ça marche?
  • Actualités
  • Tutoriels
  • Blog
  • Contact
  • FAQ
  • Ressources
Régions
  • Arabie Saoudite
  • Émirats Arabes Unis
  • Qatar
  • Bahreïn
  • Oman
  • Libye
  • Tunisie
  • Algérie
  • Maroc
Entreprise
  • Noqta, Tunisie, Tunis, téléphone +216 40 385 594
© Noqta. Tous droits réservés.