Overview
If you’ve ever wanted to give your app a friendly voice that sounds like a real person, you’re in the right place. In this tutorial we’ll build a simple AI voice assistant using ElevenLabs’ cutting‑edge text‑to‑speech (TTS) API and a Node.js backend. By the end you’ll be able to send a string of text to the API, receive high‑quality audio, and stream it back to a web client—all with just a few lines of code.
Why ElevenLabs?
Their neural TTS models produce natural‑sounding speech, support multiple languages, and even let you clone a voice from a short recording. The service is developer‑friendly, offers generous free tier limits, and integrates nicely with standard HTTP clients.
Let’s dive in.
Prerequisites
-
Node.js 18+ (for native
fetchsupport) or any recent version with a fetch polyfill. - npm or yarn for package management.
- An ElevenLabs API key – you can sign up and grab one from the dashboard at the link below.
- Basic familiarity with Express (or any HTTP framework) and front‑end JavaScript.
Getting an API Key from ElevenLabs
- Visit the ElevenLabs sign‑up page: https://try.elevenlabs.io/kr07zfuqn1bp.
- Complete the registration and navigate to the API section of your account.
- Copy the API key – you’ll need it as an environment variable.
# .env file (keep this out of version control!)
ELEVENLABS_API_KEY=your_api_key_here
Setting Up the Project
Create a fresh folder and initialize a Node project:
mkdir ai-voice-assistant
cd ai-voice-assistant
npm init -y
npm install express dotenv
We'll use the built‑in fetch (Node 18+) to talk to ElevenLabs, so no extra HTTP client is required.
Create server.js:
// server.js
import express from 'express';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
app.use(express.json());
const ELEVEN_API_KEY = process.env.ELEVENLABS_API_KEY;
const ELEVEN_TTS_ENDPOINT = 'https://api.elevenlabs.io/v1/text-to-speech';
// Helper to call ElevenLabs TTS
async function synthesizeSpeech(text, voiceId = 'EXAVITQu4vr4xnSDxMaL') {
const response = await fetch(`${ELEVEN_TTS_ENDPOINT}/${voiceId}`, {
method: 'POST',
headers: {
'xi-api-key': ELEVEN_API_KEY,
'Content-Type': 'application/json',
Accept: 'audio/mpeg',
},
body: JSON.stringify({
text,
model_id: 'eleven_monolingual_v1', // default model
voice_settings: {
stability: 0.75,
similarity_boost: 0.85,
},
}),
});
if (!response.ok) {
const err = await response.text();
throw new Error(`ElevenLabs error: ${response.status} – ${err}`);
}
// Returns a Buffer containing the MP3 audio
return Buffer.from(await response.arrayBuffer());
}
Exposing a Simple API Endpoint
Add a route that receives a JSON payload { "text": "Hello world!" } and returns the generated MP3:
app.post('/api/speak', async (req, res) => {
const { text, voiceId } = req.body;
if (!text) {
return res.status(400).json({ error: 'Missing "text" in request body' });
}
try {
const audioBuffer = await synthesizeSpeech(text, voiceId);
res.set({
'Content-Type': 'audio/mpeg',
'Content-Length': audioBuffer.length,
});
res.send(audioBuffer);
} catch (err) {
console.error(err);
res.status(500).json({ error: err.message });
}
});
Finally, start the server:
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`🚀 Server listening on http://localhost:${PORT}`));
Run it with:
node server.js
You now have a tiny micro‑service that turns any text into speech using ElevenLabs.
Streaming Audio Directly to the Browser
Instead of downloading the whole file first, you can pipe the response straight to the client. This reduces latency and feels more “assistant‑like”.
Update the route to use a ReadableStream:
app.post('/api/speak-stream', async (req, res) => {
const { text, voiceId } = req.body;
if (!text) return res.status(400).json({ error: 'Missing text' });
try {
const response = await fetch(`${ELEVEN_TTS_ENDPOINT}/${voiceId || 'EXAVITQu4vr4xnSDxMaL'}`, {
method: 'POST',
headers: {
'xi-api-key': ELEVEN_API_KEY,
'Content-Type': 'application/json',
Accept: 'audio/mpeg',
},
body: JSON.stringify({
text,
model_id: 'eleven_monolingual_v1',
voice_settings: { stability: 0.75, similarity_boost: 0.85 },
}),
});
if (!response.ok) throw new Error(`ElevenLabs error ${response.status}`);
// Forward the streaming body directly
res.setHeader('Content-Type', 'audio/mpeg');
response.body.pipeTo(new WritableStream({
write(chunk) {
res.write(chunk);
},
close() {
res.end();
},
}));
} catch (err) {
console.error(err);
res.status(500).json({ error: err.message });
}
});
On the front‑end you can now fetch the endpoint and pipe the result into an Audio element:
<button id="talkBtn">Speak</button>
<audio id="assistantAudio" controls></audio>
<script>
document.getElementById('talkBtn').addEventListener('click', async () => {
const resp = await fetch('/api/speak', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: 'Hello, I am your AI assistant!' })
});
const blob = await resp.blob();
const url = URL.createObjectURL(blob);
const audio = document.getElementById('assistantAudio');
audio.src = url;
audio.play();
});
</script>
That’s a functional voice assistant in under 100 lines of code!
Adding Voice Cloning (Optional)
ElevenLabs also lets you clone a voice from a short recording (as little as 10 seconds). Here’s a quick walkthrough:
-
Upload a sample (WAV/MP3) to the
/v1/voices/addendpoint. -
Grab the returned
voice_idand reuse it in the TTS calls above.
async function cloneVoice(samplePath, name = 'MyClone') {
const form = new FormData();
form.append('name', name);
form.append('files', await fetch(samplePath).then(r => r.blob()), 'sample.wav');
const resp = await fetch('https://api.elevenlabs.io/v1/voices/add', {
method: 'POST',
headers: { 'xi-api-key': ELEVEN_API_KEY },
body: form,
});
if (!resp.ok) throw new Error('Cloning failed');
const data = await resp.json();
return data.voice_id; // store this for later TTS calls
}
Once you have a voice_id, pass it to synthesizeSpeech or the /api/speak endpoint. Your assistant can now speak with a custom persona—perfect for brand‑specific bots or personal projects.
Debugging Tips
| Symptom | Likely Cause | Fix |
|---|---|---|
401 Unauthorized |
Wrong or missing API key | Verify ELEVENLABS_API_KEY in .env and restart the server |
| Empty audio file |
Accept header not set to audio/mpeg
|
Ensure the header is present in the request |
| Slow response | Large text payload or low stability value |
Split long passages into chunks; experiment with stability and similarity_boost settings |
Wrap‑up
We’ve built a minimal yet extensible AI voice assistant using ElevenLabs and Node.js. The core steps are:
- Obtain an API key from ElevenLabs.
- Set up a simple Express server that forwards text to the TTS endpoint.
- Stream the resulting MP3 back to the browser (or any client).
- (Optional) Clone a custom voice for a personal touch.
All of this runs on standard HTTP, so you can swap Express for Fastify, NestJS, or even a serverless function with minimal changes.
Next Steps
- Add speech‑to‑text (STT) via a service like OpenAI Whisper or Google Speech API to make the assistant truly conversational.
- Persist user preferences (voice choice, language) in a database.
- Deploy to Vercel, Render, or Railway for a public URL you can share with friends.
Ready to give your app a voice that sounds human? Grab your ElevenLabs API key now and start experimenting: https://try.elevenlabs.io/kr07zfuqn1bp.
Happy coding! 🚀
Top comments (0)