Intégrer l'API ElevenLabs dans Vos Projets Node.js : Guide Complet
Mark Toledo
16 février 2026

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
- Connectez-vous à votre compte ElevenLabs
- Cliquez sur votre avatar en haut à droite
- Allez dans Profile + API key
- 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 :
- Configuration du SDK ElevenLabs avec Node.js
- Synthèse vocale basique et streaming
- Ajustement des paramètres vocaux et voice cloning
- API REST complète avec Express, cache et rate limiting
- Intégration frontend avec Vue.js / Nuxt
- 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 !
