Server Proxy
Keep API keys secure with Express, Next.js, and Node.js proxy adapters.
Why use a proxy
Browser JavaScript is public. Any API key embedded in client-side code can be extracted and abused. The CompositeVoice server proxy injects API keys on the server, so credentials never reach the browser.
Browser Server Provider
│ │ │
│── POST /api/proxy/anthropic ──→│ │
│ (no API key) │── POST api.anthropic.com ────→│
│ │ (API key injected) │
│←── streamed response ─────────│←── streamed response ─────────│
For WebSocket providers (Deepgram, ElevenLabs, Cartesia, AssemblyAI), the proxy upgrades the connection and relays frames bidirectionally:
Browser Server Provider
│ │ │
│── WS /api/proxy/deepgram ────→│ │
│ (no API key) │── WS api.deepgram.com ──────→│
│ │ (API key in URL) │
│←─── frames ──────────────────→│←─── frames ─────────────────→│
Installation
The proxy is included in the main package:
import { createExpressProxy } from '@lukeocodes/composite-voice/proxy';
Configuration
const proxy = createExpressProxy({
// Provider API keys (include only the providers you use)
deepgramApiKey: process.env.DEEPGRAM_API_KEY,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
openaiApiKey: process.env.OPENAI_API_KEY,
groqApiKey: process.env.GROQ_API_KEY,
mistralApiKey: process.env.MISTRAL_API_KEY,
geminiApiKey: process.env.GEMINI_API_KEY,
elevenLabsApiKey: process.env.ELEVENLABS_API_KEY,
cartesiaApiKey: process.env.CARTESIA_API_KEY,
assemblyaiApiKey: process.env.ASSEMBLYAI_API_KEY,
// Route prefix (default: '/api/proxy')
pathPrefix: '/api/proxy',
// CORS origins (default: none)
cors: {
origins: ['http://localhost:3000'],
},
});
Express adapter
import express from 'express';
import { createServer } from 'http';
import { createExpressProxy } from '@lukeocodes/composite-voice/proxy';
const app = express();
const server = createServer(app);
const proxy = createExpressProxy({
deepgramApiKey: process.env.DEEPGRAM_API_KEY,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
});
// Mount HTTP routes
app.use(proxy.middleware);
// Enable WebSocket upgrades (required for Deepgram, ElevenLabs, Cartesia, AssemblyAI)
proxy.attachWebSocket(server);
server.listen(3000);
Compatible with Express 4/5, Connect, Polka, and Restify.
Next.js adapter
Create a catch-all API route at app/api/proxy/[...path]/route.ts:
import { createNextJsProxy } from '@lukeocodes/composite-voice/proxy';
const proxy = createNextJsProxy({
deepgramApiKey: process.env.DEEPGRAM_API_KEY,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
});
export const { GET, POST, PUT, DELETE, PATCH, OPTIONS } = proxy;
Note: Vercel’s serverless runtime does not support WebSocket upgrades. For WebSocket providers (Deepgram STT/TTS, ElevenLabs, Cartesia, AssemblyAI), deploy with the Node.js adapter on a platform that supports WebSockets, or use the createNodeProxy adapter on a self-hosted server.
Node.js adapter
Works with any framework that exposes http.Server:
import { createServer } from 'http';
import { createNodeProxy } from '@lukeocodes/composite-voice/proxy';
const proxy = createNodeProxy({
deepgramApiKey: process.env.DEEPGRAM_API_KEY,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
});
const server = createServer(proxy.handleRequest);
proxy.attachWebSocket(server);
server.listen(3000);
Compatible with Fastify (raw mode), Koa, Hapi, and any Node.js HTTP framework.
Route table
The proxy automatically creates routes based on which API keys you provide:
| Key provided | HTTP route | WebSocket route | Provider |
|---|---|---|---|
deepgramApiKey | — | /api/proxy/deepgram | Deepgram STT + TTS |
anthropicApiKey | /api/proxy/anthropic | — | Anthropic LLM |
openaiApiKey | /api/proxy/openai | — | OpenAI LLM + TTS |
groqApiKey | /api/proxy/groq | — | Groq LLM |
mistralApiKey | /api/proxy/mistral | — | Mistral LLM |
geminiApiKey | /api/proxy/gemini | — | Gemini LLM |
elevenLabsApiKey | — | /api/proxy/elevenlabs | ElevenLabs TTS |
cartesiaApiKey | — | /api/proxy/cartesia | Cartesia TTS |
assemblyaiApiKey | — | /api/proxy/assemblyai | AssemblyAI STT |
HTTP routes forward REST requests. WebSocket routes relay frames bidirectionally.
Client configuration
On the client, point providers at the proxy URL instead of using API keys:
// Instead of:
const stt = new DeepgramSTT({ apiKey: 'dg-...' }); // DON'T DO THIS
// Use:
const stt = new DeepgramSTT({ proxyUrl: '/api/proxy/deepgram' }); // keys stay server-side
CORS
If your frontend and backend run on different origins, configure CORS:
const proxy = createExpressProxy({
// ...keys
cors: {
origins: [
'http://localhost:5173', // Vite dev server
'https://myapp.example.com', // production
],
},
});