Skip to content

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 providedHTTP routeWebSocket routeProvider
deepgramApiKey/api/proxy/deepgramDeepgram STT + TTS
anthropicApiKey/api/proxy/anthropicAnthropic LLM
openaiApiKey/api/proxy/openaiOpenAI LLM + TTS
groqApiKey/api/proxy/groqGroq LLM
mistralApiKey/api/proxy/mistralMistral LLM
geminiApiKey/api/proxy/geminiGemini LLM
elevenLabsApiKey/api/proxy/elevenlabsElevenLabs TTS
cartesiaApiKey/api/proxy/cartesiaCartesia TTS
assemblyaiApiKey/api/proxy/assemblyaiAssemblyAI 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
    ],
  },
});

© 2026 CompositeVoice. All rights reserved.

Font size
Contrast
Motion
Transparency