---
title: "Tâches longues en Node.js : décharger l'API avec BullMQ"
description: "Analyse IA, OCR, traitement de fichiers… Les tâches longues se multiplient dans les applications web. Découvrez comment utiliser BullMQ (queues) avec NestJS pour les traiter en arrière-plan."
url: "https://lonestone.io/blog/taches-longues-nodejs-queues-avec-bullmq"
---

[Lonestone](/) ⟩ [Blog](/blog)

# Tâches longues en Node.js : traiter vos tâches sans dégrader l'expérience utilisateur avec BullMQ

8 avril 2026 · 11 min de lecture · Par Mathéo LEGER

Points clés

*   Un système de queue permet de décharger les tâches lourdes dans des workers en arrière-plan, sans bloquer le thread principal de l'API.
*   La requête HTTP retourne bien un statut 200 et place le traitement lourd dans une queue.
*   On a choisi d'utiliser BullMQ pour son intégration avec NestJS et sa simplicité de mise en place et de gestion.
*   Cette architecture s'applique à de nombreux cas concrets : analyse IA, OCR, génération de documents, import de données volumineuses.

Les applications web embarquent de plus en plus de tâches qui prennent un certain temps à être traitées. On parle de plusieurs secondes, voire plusieurs minutes. Et ceci est d’autant plus vrai depuis l’arrivée de l’IA au sein des différents services web : OCR, Analyse IA, conversations aux LLMs…

Deux questions se posent alors : comment traiter ces tâches sans bloquer le serveur ? Et comment informer l’utilisateur de leur avancement en temps réel ?

Chez Lonestone, on a adopté une combinaison simple et efficace : un **système de queues** (BullMQ) couplé à des **Server-Sent Events** (SSE). On va donc vous détailler l’architecture, les choix techniques, et on livre un exemple complet avec NestJS côté API et React côté front.

Que vos applications embarquent ou non de l’IA, ce dont on va vous parler aujourd’hui s’applique à bien des cas d’usage, et nous sommes convaincus que cela vous sera utile.

Dans ce premier article, nous aborderons le sujet des queues, de leur utilité, des différents choix qui s’offrent à nous et de comment le mettre en place dans NestJS. Par la suite, dans un second article, nous nous concentrerons sur l’utilisation des Server Sent Events et de comment les utiliser pour renvoyer l’information aux différents clients qui consomment notre API.

## Le problème des tâches longues

### Pourquoi l’utilisation d’un `await` côté API ne suffit plus

Dans la plupart des cas, lorsque l’on fait une requête HTTP classique vers notre API, l’utilisation de simple `await` suffit. Les opérations étant généralement I/O et rapides, Node.js peut les gérer efficacement sans bloquer le thread principal.

Cependant, dès qu’on touche à des requêtes HTTP longues ou à des tâches intensives pour le CPU, on se retrouve confronté à des problèmes : **Timeout de la requête**, **couplage fort**, **scalabilité** de l’application… Et en prime, une **expérience utilisateur dégradée**, car incapable de savoir où en est le traitement de la tâche.

Le couplage fort (tight coupling) entre la requête HTTP et le traitement lourd est la racine du problème. La solution passe par le découplage : déléguer le traitement à un processus en arrière-plan et répondre immédiatement au client.

### Deux problèmes distincts à résoudre

En réalité, il y a deux défis différents que l’on doit aborder :

1.  **Exécuter la tâche sans bloquer le serveur/la requête** : c’est le rôle du système de queues.
2.  **Notifier le client de l’avancée et du résultat** : c’est le rôle des événements côté serveur (SSE).

Concentrons-nous d’abord sur le système de queues pour traiter les tâches.

## Les systèmes de queues

### Qu’est-ce qu’une job queue ?

Avant de donner des pistes de solutions techniques, il est important de bien comprendre ce qu’est une queue, son fonctionnement et son utilité.

Une queue (ou file d’attente pour ceux qui ont horreur des anglicismes) est une liste de **jobs** (de tâches) en attente de traitement. Les jobs arrivent un par un dans la file, puis sont distribués à des **workers** qui les traitent en parallèle.

flowchart LR
	A@{ shape: processes, label: "Jobs" } --> B@{ "shape": "database", label: "Queue" }
	B --> C@{ shape: lin-rect, label: "Worker 1" }
	B --> D@{ shape: lin-rect, label: "Worker 2" }
	B --> E@{ shape: lin-rect, label: "Worker 3" }

Le principe est simple : le code qui reçoit la requête HTTP ajoute un job dans la queue et répond immédiatement au client. En arrière-plan, un ou plusieurs workers récupèrent les jobs et les traitent à leur rythme, indépendamment du cycle requête/réponse HTTP.

Avec ceci, on peut déjà régler un de nos problèmes : le couplage fort.

### BullMQ vs RabbitMQ : comment choisir

Maintenant il est temps de choisir la solution pour ajouter un système de queue dans vos applications. Et comme tout en informatique, il existe plein de solutions plus ou moins intéressantes selon vos besoins. Ici, nous avons décidé de nous concentrer sur 2 solutions très populaires pour créer des systèmes de queues : BullMQ et RabbitMQ.

**BullMQ**

**RabbitMQ**

**Utilisation**

Conçu pour Node.js. Idéal pour une stack JS/TS unifiée.

Agnostique. Conçu pour la communication entre micro-services (polyglotte).

**Fonctionnement**

Utilise Redis comme backend. Gestion de jobs avec priorité, retries, concurrence configurable.

Message broker pub/sub. Routing avancé entre producteurs et consommateurs.

**Complexité**

Simple à intégrer, surtout avec NestJS (`@nestjs/bullmq`).

Plus complexe à déployer et configurer (serveur dédié, exchanges, bindings).

**Cas d’usage**

Tâches en arrière-plan dans une application monolithique ou modulaire.

Communication inter-services dans une architecture micro-services.

Comme vous pouvez le voir, il n’y a pas de bon ou mauvais choix. Si je devais résumer ma vie avec vous, je dirais que c’est d’abord des rencontres. Tout va dépendre de vos besoins, de la complexité de votre système, etc…

Si vous êtes à la recherche d’un système simple et qui fonctionnera à la perfection dans une stack Javascript ou Typescript unifiée, alors BullMQ saura répondre à vos attentes. Mais si vous avez besoin d’un système plus agnostique, idéal pour un fonctionnement en micro-service et plus indépendant, alors RabbitMQ saura sûrement vous séduire.

### Pourquoi nous utilisons BullMQ avec NestJS

Chez Lonestone, la stack technique que nous utilisons nous pousse à partir sur BullMQ :

*   Premièrement, nous utilisons NestJS qui possède une intégration via le package `@nestjs/bullmq`.
*   Deuxièmement, nos applications sont souvent sans architecture micro-services.
*   Troisièmement, la simplicité de BullMQ : L’utilisation de Redis, une base de donnée clé-valeur légère, une configuration relativement simple pour choisir la concurrence, la récurrence du nettoyage, etc… Rien de bien sorcier avec BullMQ.

Bien entendu, vous pouvez fouiller plus en profondeur, chercher d’autres pistes, pour voir s’il existe des alternatives qui pourraient convenir à vos attentes spécifiques. Nous en tout cas, on a porté notre dévolu sur BullMQ.

## Place à un exemple : l’analyse du contenu d’un fichier

Faisons une pause dans la théorie, voulez-vous ? Pour rendre cela plus digeste, nous allons faire une première partie de l’exemple uniquement pour le système de queue.

Pour notre exemple, nous allons prendre un cas que nous avons eu l’occasion de rencontrer chez Lonestone : l’analyse du contenu d’un fichier pour en retirer des informations importantes.

### L’architecture du système

Comme nous l’avons vu précédemment, nous allons séparer la requête HTTP et l’analyse.

Donc lorsque l’utilisateur fera une requête POST vers l’API, il recevra bien une réponse lui disant si oui ou non la requête est un succès, puis en parallèle, nous ajoutons un job, qui une fois traité lancera l’analyse dans un worker qui fonctionnera indépendamment.

Voici donc le flux de notre système pour le moment :

sequenceDiagram
    participant Client
    participant API
    participant Queue
    participant Worker

    Client->>API: POST /analyze
    API->>Queue: Ajoute un job
    API-->>Client: 200 OK

    Queue->>Worker: Traite le job
    Worker->>Worker: Étape 1 (extraction)
    Worker->>Worker: Étape 2 (analyse)

    Worker->>Worker: Terminé

### Un peu de code : Implémentation d’une queue avec NestJS

Passons au code. L’exemple utilise NestJS avec `@nestjs/bullmq` pour les queues.

Le code que nous allons vous présenter ici est une version simplifiée de ce que vous pourrez retrouver dans le code complet de l’exemple, disponible sur [GitHub](https://github.com/lonestone/sse-queues-example). Ce projet étant basé sur la stack technique Lonestone, certains aspects peuvent être plus complexes sur le repository.

#### Initialisation du module et de la queue

La première étape est de configurer BullMQ pour NestJS. Il est configuré au niveau du module racine avec la connexion Redis :

```
// app.module.ts
@Module({
	imports: [
		BullModule.forRoot({
		  connection: {
			host: config.redis.host,
			port: config.redis.port,
		  },
		}),
		//[...]
	],
	controllers: [
		// [...]
	],
	providers: [
		// [...]
	],
})
export class AppModule {}
```

Ensuite, nous devons ajouter le `BullModule` dans le module qui nous intéresse et enregistrer la queue avec un nom. Dans notre cas, le point d’entrée est le module `AnalysisModule` qui connecte tous les éléments :

```
import { BullModule } from '@nestjs/bullmq'
import { Module } from '@nestjs/common'
import { AnalysisController } from './analysis.controller'
import { ANALYSIS_QUEUE_NAME, AnalysisProcessor } from './analysis.processor'
import { AnalysisService } from './analysis.service'

@Module({
  imports: [
    BullModule.registerQueue({ name: ANALYSIS_QUEUE_NAME }),
  ],
  controllers: [AnalysisController],
  providers: [AnalysisService, AnalysisProcessor],
})
export class AnalysisModule {}
```

On peut voir 2 étapes importantes ici pour notre système de queue :

*   `BullModule.registerQueue({ name: ANALYSIS_QUEUE_NAME })`, qui nous permet d’enregistrer la queue avec le nom que l’on choisit.
*   `AnalysisProcessor` que nous allons détailler juste après.

#### Traiter la requête envoyée par l’utilisateur

Tout d’abord nous avons notre `AnalysisController` qui contient la route permettant de lancer l’analyse.

```
import { Controller, HttpCode, HttpStatus, Param, Post } from '@nestjs/common'

@Controller('analysis')
export class AnalysisController {
  constructor(
    private readonly analysisService: AnalysisService,
  ) {}

  @Post('/:id/analyze')
  @HttpCode(HttpStatus.OK)
  async startAnalyze(@Param('id') id: string) {
    return this.analysisService.startAnalyze(id)
  }
}
```

Ce dernier possède une route `/:id/analyze` qui va appeler la méthode `startAnalyze` de notre service `AnalysisService`.

Le service est minimaliste. Il reçoit un identifiant d’analyse et ajoute un job dans la queue BullMQ :

```
import { InjectQueue } from '@nestjs/bullmq'
import { Injectable } from '@nestjs/common'
import { Queue } from 'bullmq'

@Injectable()
export class AnalysisService {
  constructor(
    @InjectQueue(ANALYSIS_QUEUE_NAME) private readonly analysisQueue: Queue,
  ) {}

  async startAnalyze(id: string) {
    this.analysisQueue.add(ANALYSIS_JOB_NAME, { analysisId: id })
  }
}
```

L’appel à `add()` est non bloquant : le job est placé dans Redis et le service retourne immédiatement. Le traitement effectif se fera dans le worker.

#### Le processor (worker) : traiter le job

Le processor est le composant qui effectue le travail lourd. Il hérite de `WorkerHost` de BullMQ qui attend donc une méthode `process` permettant de traiter le job. C’est ici qu’on placera les tâches longues que l’on veut séparer de notre requête principale :

```
import { Processor, WorkerHost } from '@nestjs/bullmq'
import { Job } from 'bullmq'

export const ANALYSIS_QUEUE_NAME = 'analysis_queue'
export const ANALYSIS_JOB_NAME = 'analysis_job'
export const ANALYSIS_JOBS_CONCURRENCY = 10

@Processor(ANALYSIS_QUEUE_NAME, {
  concurrency: ANALYSIS_JOBS_CONCURRENCY,
  removeOnComplete: { age: 3600, count: 1000 },
  removeOnFail: { age: 24 * 3600 },
})
export class AnalysisProcessor extends WorkerHost {
  constructor() {
    super()
  }

  async process(job: Job<AnalysisJobData>) {
    await performExtraction(job.data) // Tâche longue
    await performAnalysis(job.data) // Tâche longue
  }
}
```

Quelques points importants :

*   **`concurrency: 10`** : jusqu’à 10 jobs sont traités en parallèle par ce worker.
*   **`removeOnComplete` et `removeOnFail`** : les jobs terminés ou échoués sont nettoyés automatiquement pour éviter que Redis ne grossisse indéfiniment.

### Une petite partie côté front avec React

#### Déclencher l’analyse

Côté front, on crée un hook de mutation simple : il appelle l’API pour lancer l’analyse. Le retour est immédiat (puisque le serveur ajoute le job en queue et répond tout de suite). Les mises à jour de progression arrivent ensuite via le SSE.

```
import { useMutation } from '@tanstack/react-query'

export function useStartAnalysis() {
  return useMutation({
    mutationFn: async ({ id }: { id: string }) => {
      const response = await analysisControllerStartAnalyze({
        path: { id },
      })
      if (response.error) throw new Error(response.error as string)
      return response.data
    },
  })
}
```

À noter que nous utilisons un SDK auto-généré pour nos routes, d’où l’utilisation de `analysisControllerStartAnalyze` pour notre requête POST sur l’endpoint `/:id/analyze`.

#### Lancer une analyse

Dans notre exemple, nous avons un dashboard affichant quelques “analyses”. L’interface du dashboard utilise le hook et affiche une carte par analyse :

```
export default function DashboardPage() {
  const { data: analyses } = useAnalyses()
  const { mutate } = useStartAnalysis()

  return (
    <main className="container mx-auto py-8 px-4 space-y-6">
      <h1 className="text-3xl font-bold">Analyses</h1>
      {analyses?.map((analysis) => (
        <Card key={analysis.id}>
          <CardHeader>
            <CardTitle>Analysis</CardTitle>
            <AnalysisBadge step={analysis.step} />
          </CardHeader>
          <CardFooter>
            <Button
              onClick={() => mutate({ id: analysis.id })}
              disabled={
                analysis.step !== 'completed' && analysis.step !== 'failed'
              }
            >
              Lancer l'analyse
            </Button>
          </CardFooter>
        </Card>
      ))}
    </main>
  )
}
```

On peut donc lancer une analyse pour chaque “analyse” (désolé pour la logique métier, on aura déjà vu des règles métiers plus intelligentes que celle-là).

Mais si vous avez suivi les étapes et que vous avez lancé une analyse, vous avez sûrement remarqué que nous n’avons pas de retour sur l’avancée de notre analyse, ce qui fait que nous ne pouvons pas savoir si une analyse s’est correctement terminée. Tout ce que nous sommes capables de déterminer pour le moment c’est si l’analyse s’est bien lancée ou non (si nous recevons bien une réponse HTTP 200 pour notre requête POST).

## En résumé

Pour traiter des tâches lourdes en Node.js, nous avons décidé d’utiliser BullMQ, qui permet une architecture simple et performante :

*   **BullMQ** gère le traitement en arrière-plan via Redis, avec concurrence, retries et nettoyage automatique.
*   La requête HTTP retourne immédiatement un statut 200, sans attendre la fin du traitement.
*   Les workers traitent les jobs de façon asynchrone, indépendamment du cycle requête/réponse.

Ce système s’adapte à de nombreux cas concrets : analyse par IA, traitement OCR, génération de documents, import de données, envoi d’emails en masse…

Mais une question subsiste : comment prévenir les utilisateurs de l’état d’avancement du traitement ?

À l’heure actuelle, nous sommes capables de répondre aux besoins de performances et de réduire le couplage, mais nous n’avons pas encore vu comment envoyer des informations aux différents clients qui ont envoyé la requête HTTP.

C’est précisément l’objet de notre deuxième article (nous sommes persuadés que vous ne l’aviez pas vu venir) : comment utiliser les **Server-Sent Events** (SSE) et des principes d’architecture Event-Driven dans NestJS pour tenir les utilisateurs informés en temps réel !

Retrouvez la suite [dans notre deuxième article sur le sujet](taches-longues-nodejs-notifier-avec-sse), et le code complet de l’exemple (combinant BullMQ et SSE) sur [GitHub](https://github.com/lonestone/sse-queues-example).

![Tâches longues en Node.js : décharger l'API avec BullMQ](/.netlify/images?url=_astro%2Fthumbnail.DBHJEGIf.png&w=1920&h=1080&dpl=69e9deed41371b000857cf89)

Sommaire

[1 / Le problème des tâches longues](#le-problème-des-tâches-longues) [2 / Les systèmes de queues](#les-systèmes-de-queues) [3 / Place à un exemple : l’analyse du contenu d’un fichier](#place-à-un-exemple--lanalyse-du-contenu-dun-fichier) [4 / En résumé](#en-résumé)

Sommaire 1 / Le problème des tâches longues 2 / Les systèmes de queues 3 / Place à un exemple : l’analyse du contenu d’un fichier 4 / En résumé

Lonestone est une agence qui conçoit et développe des produits web et mobile innovants intégrant de l'IA.

Nos experts partagent leurs expériences sur le blog. Contactez-nous pour discuter de vos projets !

[En savoir plus sur Lonestone](/)

[Parler à un expert](/contact)

![](/_astro/use-case-corner.3qL77H9h.png)

Besoin d'accompagnement ?

Nos experts sont disponibles pour discuter de votre projet.

[Parler à un expert](/contact)

## On continue la lecture ?

[Tous les articles](/blog)

[![Tâches longues en Node.js : notifier les utilisateurs de l'avancée des tâches via SSE](/.netlify/images?url=_astro%2Fthumbnail.BgYOQJGu.png&w=1920&h=1080&dpl=69e9deed41371b000857cf89)

Tâches longues en Node.js : notifier les utilisateurs de l'avancée des tâches via SSE

Analyse IA, OCR, traitement de fichiers… Les tâches longues se multiplient dans les applications web. Découvrez comment utiliser les Server-Sent Events (SSE) avec NestJS et React pour notifier vos utilisateurs en temps réel.



](/blog/taches-longues-nodejs-notifier-avec-sse)[![How to build a strongly typed REST API with Nest.js](/.netlify/images?url=_astro%2Fthumbnail.DPWZucwY.webp&w=1920&h=1129&dpl=69e9deed41371b000857cf89)

How to build a strongly typed REST API with Nest.js

Generate a strongly typed SDK from your Nest.js API to avoid manual endpoint sync and reduce dev-time errors.



](/blog/generating-a-strongly-typed-rest-api-for-nest-js)[![Réussir son test driven development : Nos astuces ](/.netlify/images?url=_astro%2Fthumbnail.DSuxsDGF.jpg&w=1200&h=973&dpl=69e9deed41371b000857cf89)

Réussir son test driven development : Nos astuces

Découvrez comment le Test Driven Development peut transformer votre processus de développement, améliorer votre code et renforcer la cohésion d'équipe.



](/blog/test-driven-development)

[Tous les articles](/blog)

## Nos guides

### Guide de l'IA générative

[

01\. IA, Machine Learning & Deep Learning



](/ai/deep-learning)[

02\. LLM (Large Language Model) : définition et fonctionnement



](/ai/llm)[

03\. RAG + MCP : Architecture pour agents IA



](/ai/rag-mcp)[

04\. Agents IA : définition et cas d'usage



](/ai/agents-ia)[

05\. Boostez vos logiciels avec l'intégration de l'IA



](/ai/integration-ia)[

06\. Meilleurs outils IA pour les entreprises



](/ai/outils-ia)

### Guide pour créer un SaaS IA

[

01\. Construire un SaaS IA rentable : la méthode qui fonctionne



](/creer-saas-ia/roadmap)[

02\. Comparatif LLM 2026 : quel modèle choisir pour votre SaaS ?



](/creer-saas-ia/comparatif-llm-saas)[

03\. Héberger un SaaS IA en France : performances, coûts et souveraineté



](/creer-saas-ia/heberger-ia)[

04\. Framework d’évaluation IA : pourquoi et comment le mettre en place



](/creer-saas-ia/evaluation)[

05\. MCP (Model Context Protocol) : le standard pour connecter IA et logiciels



](/creer-saas-ia/mcp)[

06\. Bien choisir entre RAG et ses alternatives : ce que personne ne vous explique vraiment



](/creer-saas-ia/rag)[

07\. Comment concevoir un copilote IA qui crée de la vraie valeur dans un SaaS



](/creer-saas-ia/copilote-ia)[

08\. POC IA : comment valider votre idée avant d’investir dans un SaaS IA



](/creer-saas-ia/poc)

### Blog de Lonestone

Retrouvez nos derniers articles sur le développement web, l'IA, le product design et les retours d'expérience de nos projets.

[Accéder au blog](/blog)

Lonestone apporte son expertise product à 200+ grands comptes, PME et startups depuis 11 ans.

Avec notre équipe senior et nos méthodes rodées, vous pouvez comptez sur une livraison rapide d'un produit robuste vraiment utile.

## Nos solutions

[

01\. Intégration IA pour éditeurs logiciels



](/solutions/integration-ia-editeurs)[

02\. Automatisation de process avec IA



](/solutions/automatisation-process-ia)[

03\. Création de SaaS pour startup



](/solutions/creation-saas)[

04\. Développement d'outils internes



](/solutions/developpement-outil-metier)[

05\. Création d'app mobile



](/solutions/creation-app-mobile)[

06\. Création de site web



](/solutions/creation-site-web)[

07\. Création de CRM sur mesure



](/solutions/crm-sur-mesure)

## On discute de votre projet ?

Échange gratuit et sans engagement, directement avec un expert du sujet. Devis sous 48h.

[Demander un devis](/contact) Prendre rdv

Contacter l'équipe  
de Lonestone
