Tutoriels

Intégrer l'API ElevenLabs dans Vos Projets Node.js : Guide Complet

Mark Toledo

Mark Toledo

16 février 2026

Intégrer l'API ElevenLabs dans Vos Projets Node.js : Guide Complet

La synthèse vocale par IA a fait un bond spectaculaire ces dernières années. Fini les voix robotiques et monotones : les modèles actuels produisent un audio quasi indiscernable d'un humain. Parmi les solutions disponibles, ElevenLabs s'est imposé comme la référence en termes de qualité vocale et de flexibilité API. Dans ce tutoriel, on va intégrer leur SDK Node.js dans un projet concret, du premier appel text-to-speech jusqu'à une API REST complète avec streaming audio.

Pourquoi ajouter la synthèse vocale à vos applications ?

Les cas d'usage sont nombreux et en pleine expansion :

  • Accessibilité : rendre vos contenus accessibles aux personnes malvoyantes ou dyslexiques
  • Podcasts automatisés : transformer vos articles de blog en épisodes audio
  • Assistants vocaux : créer des chatbots qui parlent naturellement
  • E-learning : générer des narrations pour vos modules de formation
  • Applications multilingues : produire des versions audio dans plusieurs langues
  • Jeux vidéo et fiction interactive : donner une voix unique à chaque personnage

Avec une API bien intégrée, vous pouvez automatiser tout ça directement depuis votre backend Node.js.

Prérequis

Avant de commencer, assurez-vous d'avoir :

Outil Version Notes
Node.js 20+ LTS recommandé, support ESM natif
npm 10+ Livré avec Node.js 20+
Compte ElevenLabs - Créer un compte gratuitement
Clé API - Disponible dans le dashboard ElevenLabs

Le plan gratuit d'ElevenLabs offre 10 000 caractères par mois, ce qui est largement suffisant pour suivre ce tutoriel et expérimenter. Vous pourrez passer à un plan supérieur quand votre projet sera en production.

Récupérer votre clé API

  1. Connectez-vous à votre compte ElevenLabs
  2. Cliquez sur votre avatar en haut à droite
  3. Allez dans Profile + API key
  4. Copiez votre clé API (elle commence généralement par sk_)

Installation et configuration

Initialiser le projet

mkdir elevenlabs-tts-demo
cd elevenlabs-tts-demo
npm init -y

Activez les modules ES dans votre package.json :

{
  "name": "elevenlabs-tts-demo",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js"
  }
}

Installer les dépendances

npm install elevenlabs dotenv

Le package elevenlabs est le SDK officiel Node.js. Il encapsule toutes les routes de l'API REST et fournit un typage TypeScript complet.

Variables d'environnement

Créez un fichier .env à la racine de votre projet :

# .env
ELEVENLABS_API_KEY=sk_votre_cle_api_ici

Et ajoutez .env à votre .gitignore :

echo ".env" >> .gitignore

Configuration du client

// src/config.js
import 'dotenv/config';
import { ElevenLabsClient } from 'elevenlabs';

if (!process.env.ELEVENLABS_API_KEY) {
  throw new Error('La variable ELEVENLABS_API_KEY est requise');
}

export const client = new ElevenLabsClient({
  apiKey: process.env.ELEVENLABS_API_KEY,
});

C'est tout pour le setup. Le client est prêt à être importé n'importe où dans votre projet.

Premier appel API : Text-to-Speech basique

Commençons par le cas le plus simple : convertir un texte en fichier audio.

// src/index.js
import { writeFile } from 'fs/promises';
import { client } from './config.js';

async function textToSpeech() {
  // Générer l'audio à partir du texte
  const audio = await client.textToSpeech.convert('JBFqnCBsd6RMkjVDRZzb', {
    text: 'Bonjour ! Bienvenue sur Atinux, le blog où vous allez aimer le JavaScript.',
    model_id: 'eleven_multilingual_v2',
    output_format: 'mp3_44100_128',
  });

  // Collecter les chunks du stream en un seul Buffer
  const chunks = [];
  for await (const chunk of audio) {
    chunks.push(chunk);
  }
  const buffer = Buffer.concat(chunks);

  // Sauvegarder le fichier audio
  await writeFile('output.mp3', buffer);
  console.log('Fichier audio généré : output.mp3');
}

textToSpeech().catch(console.error);

Exécutez avec node src/index.js et vous obtiendrez un fichier output.mp3 avec une voix naturelle qui lit votre texte. L'identifiant JBFqnCBsd6RMkjVDRZzb correspond à la voix "George" (anglais, mais le modèle multilingue gère très bien le français).

Comprendre les paramètres clés

Paramètre Description Valeurs courantes
model_id Modèle de synthèse vocale eleven_multilingual_v2 (multilingue), eleven_turbo_v2_5 (rapide)
output_format Format audio de sortie mp3_44100_128, pcm_16000, ulaw_8000
voice_id Identifiant de la voix Voir section suivante

Fonctionnalités avancées

Lister et choisir une voix

ElevenLabs propose une bibliothèque de voix prédéfinies. Voici comment les explorer :

// src/list-voices.js
import { client } from './config.js';

async function listVoices() {
  const response = await client.voices.getAll();

  console.log('Voix disponibles :\n');
  for (const voice of response.voices) {
    console.log(`- ${voice.name} (${voice.voice_id})`);
    console.log(`  Labels : ${JSON.stringify(voice.labels)}`);
    console.log(`  Aperçu : ${voice.preview_url ?? 'non disponible'}\n`);
  }
}

listVoices().catch(console.error);

Quelques voix populaires pour le français :

Voix ID Style
George JBFqnCBsd6RMkjVDRZzb Narration, podcast
Charlotte XB0fDUnXU5powFXDhCwa Douce, professionnelle
Aria 9BWtsMINqrJLrRacOk9x Expressive, dynamique

Ajuster les paramètres vocaux

Vous pouvez affiner le rendu avec les voice_settings :

// src/advanced-tts.js
import { writeFile } from 'fs/promises';
import { client } from './config.js';

async function advancedTTS() {
  const audio = await client.textToSpeech.convert('JBFqnCBsd6RMkjVDRZzb', {
    text: `L'intelligence artificielle révolutionne le développement web.
           Avec Node.js et les bonnes API, vous pouvez créer des expériences
           utilisateur incroyables en quelques lignes de code.`,
    model_id: 'eleven_multilingual_v2',
    output_format: 'mp3_44100_128',
    voice_settings: {
      stability: 0.5,         // 0-1 : stabilité de la voix (bas = plus expressif)
      similarity_boost: 0.75, // 0-1 : fidélité à la voix originale
      style: 0.3,             // 0-1 : exagération du style (attention aux valeurs hautes)
      use_speaker_boost: true, // Améliore la clarté
    },
  });

  const chunks = [];
  for await (const chunk of audio) {
    chunks.push(chunk);
  }
  await writeFile('output-advanced.mp3', Buffer.concat(chunks));
  console.log('Audio avancé généré avec succès');
}

advancedTTS().catch(console.error);

Conseils d'ajustement :

  • Stabilité basse (0.2-0.4) : voix plus vivante, parfaite pour la narration
  • Stabilité haute (0.7-1.0) : voix plus constante, idéale pour les assistants
  • Similarity boost haut : reste proche de la voix de référence
  • Style : à utiliser avec parcimonie, les valeurs au-dessus de 0.5 peuvent donner des résultats exagérés

Streaming audio en temps réel

Pour les applications interactives, attendre la génération complète n'est pas envisageable. Le streaming permet de commencer à jouer l'audio dès les premiers octets :

// src/stream-tts.js
import { createWriteStream } from 'fs';
import { pipeline } from 'stream/promises';
import { Readable } from 'stream';
import { client } from './config.js';

async function streamTTS() {
  const audioStream = await client.textToSpeech.convertAsStream(
    'JBFqnCBsd6RMkjVDRZzb',
    {
      text: 'Le streaming audio permet de réduire la latence perçue et offre une meilleure expérience utilisateur.',
      model_id: 'eleven_turbo_v2_5', // Modèle optimisé pour le streaming
      output_format: 'mp3_44100_128',
    }
  );

  // Pipe directement vers un fichier (ou vers une réponse HTTP)
  const outputStream = createWriteStream('output-stream.mp3');
  const readable = Readable.from(audioStream);
  await pipeline(readable, outputStream);

  console.log('Stream audio terminé');
}

streamTTS().catch(console.error);

Le modèle eleven_turbo_v2_5 est spécialement optimisé pour le streaming : il offre une latence réduite tout en conservant une excellente qualité vocale.

Voice Cloning via l'API

ElevenLabs permet de cloner une voix à partir d'échantillons audio. C'est extrêmement puissant pour créer des voix personnalisées :

// src/clone-voice.js
import { createReadStream } from 'fs';
import { client } from './config.js';

async function cloneVoice() {
  const voice = await client.voices.add({
    name: 'Ma Voix Personnalisée',
    description: 'Clone de voix pour mon application',
    files: [
      // Fournir 1 à 25 échantillons audio (min 1 minute au total)
      createReadStream('samples/echantillon-1.mp3'),
      createReadStream('samples/echantillon-2.mp3'),
    ],
    labels: {
      language: 'fr',
      use_case: 'narration',
    },
  });

  console.log(`Voix clonée créée : ${voice.voice_id}`);
  console.log(`Nom : ${voice.name}`);

  // Utiliser immédiatement la voix clonée
  const audio = await client.textToSpeech.convert(voice.voice_id, {
    text: 'Cette phrase est prononcée avec ma voix clonée !',
    model_id: 'eleven_multilingual_v2',
  });

  // ... sauvegarder l'audio
}

cloneVoice().catch(console.error);

Note importante : le voice cloning nécessite un plan payant (Starter minimum). Assurez-vous d'avoir les droits sur les échantillons audio utilisés.

Projet pratique : API REST de synthèse vocale avec Express

Passons aux choses sérieuses. On va construire une API REST complète qui expose un endpoint de synthèse vocale.

Installation des dépendances

npm install express cors helmet

Le serveur complet

// src/server.js
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { Readable } from 'stream';
import { pipeline } from 'stream/promises';
import { client } from './config.js';

const app = express();
const PORT = process.env.PORT || 3000;

app.use(helmet());
app.use(cors());
app.use(express.json());

// Cache simple en mémoire (utiliser Redis en production)
const audioCache = new Map();

// POST /api/synthesize - Synthèse vocale
app.post('/api/synthesize', async (req, res) => {
  try {
    const {
      text,
      voiceId = 'JBFqnCBsd6RMkjVDRZzb',
      modelId = 'eleven_multilingual_v2',
      stability = 0.5,
      similarityBoost = 0.75,
      outputFormat = 'mp3_44100_128',
    } = req.body;

    // Validation
    if (!text || typeof text !== 'string') {
      return res.status(400).json({ error: 'Le champ "text" est requis' });
    }

    if (text.length > 5000) {
      return res.status(400).json({ error: 'Le texte ne doit pas dépasser 5000 caractères' });
    }

    // Vérifier le cache
    const cacheKey = `${voiceId}-${modelId}-${stability}-${similarityBoost}-${text}`;
    if (audioCache.has(cacheKey)) {
      console.log('Cache hit !');
      const cachedBuffer = audioCache.get(cacheKey);
      res.set({
        'Content-Type': 'audio/mpeg',
        'Content-Length': cachedBuffer.length,
        'X-Cache': 'HIT',
      });
      return res.send(cachedBuffer);
    }

    // Générer l'audio
    const audioStream = await client.textToSpeech.convertAsStream(voiceId, {
      text,
      model_id: modelId,
      output_format: outputFormat,
      voice_settings: {
        stability,
        similarity_boost: similarityBoost,
      },
    });

    // Collecter pour le cache ET streamer vers le client
    const chunks = [];
    res.set({
      'Content-Type': 'audio/mpeg',
      'Transfer-Encoding': 'chunked',
      'X-Cache': 'MISS',
    });

    const readable = Readable.from(audioStream);

    readable.on('data', (chunk) => {
      chunks.push(chunk);
      res.write(chunk);
    });

    readable.on('end', () => {
      const fullBuffer = Buffer.concat(chunks);
      audioCache.set(cacheKey, fullBuffer);

      // Limiter la taille du cache (100 entrées max)
      if (audioCache.size > 100) {
        const firstKey = audioCache.keys().next().value;
        audioCache.delete(firstKey);
      }

      res.end();
    });

    readable.on('error', (err) => {
      console.error('Erreur de streaming :', err);
      if (!res.headersSent) {
        res.status(500).json({ error: 'Erreur lors de la génération audio' });
      }
    });
  } catch (error) {
    console.error('Erreur de synthèse :', error.message);
    res.status(500).json({ error: 'Erreur interne du serveur' });
  }
});

// GET /api/voices - Lister les voix disponibles
app.get('/api/voices', async (req, res) => {
  try {
    const response = await client.voices.getAll();
    const voices = response.voices.map((v) => ({
      id: v.voice_id,
      name: v.name,
      labels: v.labels,
      previewUrl: v.preview_url,
    }));
    res.json({ voices });
  } catch (error) {
    console.error('Erreur :', error.message);
    res.status(500).json({ error: 'Impossible de récupérer les voix' });
  }
});

// Health check
app.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    cacheSize: audioCache.size,
    uptime: process.uptime(),
  });
});

app.listen(PORT, () => {
  console.log(`Serveur TTS démarré sur http://localhost:${PORT}`);
});

Tester l'API

# Démarrer le serveur
node src/server.js

# Lister les voix
curl http://localhost:3000/api/voices | jq .

# Synthétiser du texte (le résultat est un fichier MP3)
curl -X POST http://localhost:3000/api/synthesize \
  -H "Content-Type: application/json" \
  -d '{"text": "Bonjour depuis notre API de synthèse vocale !"}' \
  --output test.mp3

# Jouer le fichier (macOS)
afplay test.mp3

Intégration avec un frontend Vue.js / Nuxt

Côté client, voici un composant Vue 3 qui consomme notre API :

<script setup>
import { ref, reactive } from 'vue';

const state = reactive({
  text: '',
  voiceId: 'JBFqnCBsd6RMkjVDRZzb',
  loading: false,
  error: null,
});

const audioUrl = ref(null);
const audioRef = ref(null);

async function synthesize() {
  if (!state.text.trim()) return;

  state.loading = true;
  state.error = null;

  // Libérer l'URL précédente
  if (audioUrl.value) {
    URL.revokeObjectURL(audioUrl.value);
  }

  try {
    const response = await fetch('/api/synthesize', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        text: state.text,
        voiceId: state.voiceId,
      }),
    });

    if (!response.ok) {
      throw new Error(`Erreur HTTP : ${response.status}`);
    }

    const blob = await response.blob();
    audioUrl.value = URL.createObjectURL(blob);

    // Lecture automatique
    await nextTick();
    audioRef.value?.play();
  } catch (err) {
    state.error = err.message;
  } finally {
    state.loading = false;
  }
}
</script>

<template>
  <div class="tts-player">
    <textarea
      v-model="state.text"
      placeholder="Entrez votre texte ici..."
      rows="4"
    />

    <select v-model="state.voiceId">
      <option value="JBFqnCBsd6RMkjVDRZzb">George</option>
      <option value="XB0fDUnXU5powFXDhCwa">Charlotte</option>
      <option value="9BWtsMINqrJLrRacOk9x">Aria</option>
    </select>

    <button @click="synthesize" :disabled="state.loading || !state.text.trim()">
      {{ state.loading ? 'Génération en cours...' : 'Lire le texte' }}
    </button>

    <audio v-if="audioUrl" ref="audioRef" :src="audioUrl" controls />

    <p v-if="state.error" class="error">{{ state.error }}</p>
  </div>
</template>

Pour un projet Nuxt 3, utilisez useFetch ou $fetch au lieu de fetch natif, et configurez le proxy API dans nuxt.config.ts pour éviter les problèmes CORS en développement :

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/api/**': { proxy: 'http://localhost:3000/api/**' },
  },
});

Optimisations et bonnes pratiques

1. Gestion des erreurs et retry

L'API ElevenLabs peut occasionnellement retourner des erreurs 429 (rate limit) ou 500. Implémentez un mécanisme de retry :

// src/utils/retry.js
export async function withRetry(fn, maxRetries = 3, baseDelay = 1000) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      const isRetryable =
        error.statusCode === 429 ||
        error.statusCode >= 500;

      if (!isRetryable || attempt === maxRetries) {
        throw error;
      }

      // Backoff exponentiel avec jitter
      const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 500;
      console.warn(`Tentative ${attempt}/${maxRetries} échouée. Retry dans ${Math.round(delay)}ms...`);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
}

// Utilisation
import { withRetry } from './utils/retry.js';

const audio = await withRetry(() =>
  client.textToSpeech.convert('JBFqnCBsd6RMkjVDRZzb', {
    text: 'Texte à convertir',
    model_id: 'eleven_multilingual_v2',
  })
);

2. Rate limiting côté serveur

Protégez votre API et votre quota ElevenLabs avec un rate limiter :

npm install express-rate-limit
import rateLimit from 'express-rate-limit';

const ttsLimiter = rateLimit({
  windowMs: 60 * 1000,   // Fenêtre d'une minute
  max: 10,                // 10 requêtes par minute par IP
  message: { error: 'Trop de requêtes. Réessayez dans une minute.' },
  standardHeaders: true,
});

app.post('/api/synthesize', ttsLimiter, async (req, res) => {
  // ... votre handler
});

3. Estimer ses coûts

ElevenLabs facture au caractère. Voici un tableau pour estimer vos coûts :

Plan Caractères/mois Prix Coût par 1000 car.
Free 10 000 0 € Gratuit
Starter 30 000 ~5 $/mois ~0,17 $
Creator 100 000 ~22 $/mois ~0,22 $
Pro 500 000 ~99 $/mois ~0,20 $
Scale 2 000 000 ~330 $/mois ~0,17 $

Astuce : un article de blog moyen fait environ 5 000 à 10 000 caractères. Avec le plan Starter, vous pouvez convertir 3 à 6 articles par mois en audio. Le cache implémenté plus haut évite de reconsommer des caractères pour les mêmes textes.

4. Monitoring de l'usage API

Surveillez votre consommation pour éviter les surprises :

// src/utils/usage.js
import { client } from '../config.js';

export async function checkUsage() {
  const subscription = await client.user.getSubscription();

  const used = subscription.character_count;
  const limit = subscription.character_limit;
  const percent = ((used / limit) * 100).toFixed(1);

  console.log(`Usage : ${used.toLocaleString()} / ${limit.toLocaleString()} caractères (${percent}%)`);

  if (used / limit > 0.8) {
    console.warn('Attention : plus de 80% du quota utilisé !');
  }

  return { used, limit, percent: parseFloat(percent) };
}

Déploiement en production

Variables d'environnement sécurisées

Ne stockez jamais votre clé API dans le code. En production, utilisez les secrets de votre plateforme d'hébergement :

# Railway / Render / Fly.io
# Configurez ELEVENLABS_API_KEY dans le dashboard

# Docker
docker run -e ELEVENLABS_API_KEY=sk_xxx mon-app-tts

# PM2 avec ecosystem
# ecosystem.config.cjs
module.exports = {
  apps: [{
    name: 'tts-api',
    script: 'src/server.js',
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000,
      // Clé chargée depuis le secret manager
    },
  }],
};

Checklist de production

Avant de déployer, vérifiez ces points :

  • Clé API dans les variables d'environnement (pas dans le code)
  • Rate limiting activé sur les endpoints de synthèse
  • Cache en place (Redis recommandé en production, plutôt que Map)
  • Monitoring de l'usage API (alerte à 80% du quota)
  • Gestion des erreurs avec retry et backoff exponentiel
  • CORS configuré pour votre domaine uniquement
  • Helmet activé pour les headers de sécurité
  • Logs structurés pour le debugging

Conclusion : à vous de jouer

Vous avez maintenant toutes les clés pour intégrer la synthèse vocale IA dans vos projets Node.js. De l'appel API basique au streaming en temps réel, en passant par une API REST complète et un composant Vue.js, vous pouvez bâtir des expériences audio riches et naturelles.

Pour résumer ce qu'on a couvert :

  1. Configuration du SDK ElevenLabs avec Node.js
  2. Synthèse vocale basique et streaming
  3. Ajustement des paramètres vocaux et voice cloning
  4. API REST complète avec Express, cache et rate limiting
  5. Intégration frontend avec Vue.js / Nuxt
  6. Bonnes pratiques de production

La prochaine étape ? Créez votre compte ElevenLabs, récupérez votre clé API et lancez-vous. Commencez par le projet Express présenté ici, puis adaptez-le à vos besoins. La qualité vocale d'ElevenLabs est vraiment impressionnante, et leur SDK Node.js rend l'intégration accessible même pour un projet personnel.

Si vous voulez aller plus loin, explorez le voice cloning pour créer des voix uniques pour vos projets, ou combinez la synthèse vocale avec un LLM pour créer un assistant vocal conversationnel complet. Les possibilités sont énormes.

Bon code, et à très bientôt sur Atinux !