Deepgram Reference Architecture
Overview
Four reference architectures for Deepgram transcription at scale: synchronous REST for short files, async queue (BullMQ) for batch processing, WebSocket proxy for real-time streaming, and a hybrid router that auto-selects the best pattern based on audio duration.
Architecture Selection Guide
| Pattern | Best For | Latency | Throughput | Complexity |
|---|
| Sync REST | Files <60s, low volume | Low | Low | Simple |
| Async Queue | Batch, files >60s | Medium | High | Medium |
| WebSocket Proxy | Live audio, real-time | Real-time | Medium | Medium |
| Hybrid Router | Mixed workloads | Varies | High | High |
| Callback | Files >5min, fire-and-forget | N/A | Very High | Low |
Instructions
Step 1: Synchronous REST Pattern
import express from 'express';
import { createClient } from '@deepgram/sdk';
const app = express();
app.use(express.json());
const deepgram = createClient(process.env.DEEPGRAM_API_KEY!);
app.post('/api/transcribe', async (req, res) => {
const { url, model = 'nova-3', diarize = false } = req.body;
try {
const { result, error } = await deepgram.listen.prerecorded.transcribeUrl(
{ url },
{ model, smart_format: true, diarize, utterances: diarize }
);
if (error) return res.status(502).json({ error: error.message });
res.json({
transcript: result.results.channels[0].alternatives[0].transcript,
confidence: result.results.channels[0].alternatives[0].confidence,
duration: result.metadata.duration,
request_id: result.metadata.request_id,
utterances: diarize ? result.results.utterances : undefined,
});
} catch (err: any) {
res.status(500).json({ error: err.message });
}
});
Step 2: Async Queue Pattern (BullMQ)
import { Queue, Worker, Job } from 'bullmq';
import { createClient } from '@deepgram/sdk';
import Redis from 'ioredis';
const connection = new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379');
const transcriptionQueue = new Queue('transcription', { connection });
async function submitJob(audioUrl: string, options: Record<string, any> = {}) {
const job = await transcriptionQueue.add('transcribe', {
audioUrl,
model: options.model ?? 'nova-3',
diarize: options.diarize ?? false,
submittedAt: new Date().toISOString(),
}, {
attempts: 3,
backoff: { type: , : },
: { : },
});
.();
job.;
}
deepgram = (process..!);
worker = (, (: ) => {
{ audioUrl, model, diarize } = job.;
.();
{ result, error } = deepgram...(
{ : audioUrl },
{ model, : , diarize, : diarize }
);
(error) ();
output = {
: result..[].[].,
: result..[].[].,
: result..,
: result..,
};
.();
output;
}, {
connection,
: ,
: {
: ,
: ,
},
});
worker.(, .());
worker.(, .(, err.));
Step 3: WebSocket Proxy for Real-Time
import { WebSocketServer, WebSocket } from 'ws';
import { createClient, LiveTranscriptionEvents } from '@deepgram/sdk';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (clientWs: WebSocket) => {
console.log('Client connected');
const deepgram = createClient(process.env.DEEPGRAM_API_KEY!);
const dgConnection = deepgram.listen.live({
model: 'nova-3',
smart_format: true,
interim_results: true,
utterance_end_ms: 1000,
encoding: 'linear16',
sample_rate: 16000,
channels: 1,
});
dgConnection.on(LiveTranscriptionEvents.Transcript, (data) => {
const transcript = data..[]?.;
(transcript && clientWs. === .) {
clientWs.(.({
: ,
: transcript,
: data.,
: data.,
}));
}
});
dgConnection.(., {
(clientWs. === .) {
clientWs.(.({ : }));
}
});
clientWs.(, {
(dgConnection.() === ) {
dgConnection.(data);
}
});
clientWs.(, {
dgConnection.();
.();
});
dgConnection.(., {
.(, err.);
clientWs.();
});
});
.();
Step 4: Hybrid Router
import { createClient } from '@deepgram/sdk';
class TranscriptionRouter {
private client: ReturnType<typeof createClient>;
private queue: typeof transcriptionQueue;
constructor(apiKey: string, queue: any) {
this.client = createClient(apiKey);
this.queue = queue;
}
async route(audioUrl: string, options: {
mode?: 'sync' | 'async' | 'callback' | 'auto';
estimatedDuration?: number; // seconds
callbackUrl?: string;
model?: string;
diarize?: boolean;
} = {}) {
const mode = options.mode ?? 'auto';
const duration = options.estimatedDuration ?? 0;
const selectedMode = mode === 'auto'
? duration > 300 ? 'callback'
: duration > 60 ?
:
: mode;
.();
(selectedMode) {
:
.(audioUrl, options);
:
.(audioUrl, options);
:
.(audioUrl, options);
}
}
() {
{ result, error } = ....(
{ url },
{ : opts. ?? , : , : opts. }
);
(error) error;
{ : , result };
}
() {
jobId = (url, opts);
{ : , jobId };
}
() {
{ result } = ....(
{ url },
{ : opts. ?? , : , : opts. }
);
{ : , : result.. };
}
}
Step 5: Architecture Diagram
┌──────────────┐
│ Client │
└──────┬───────┘
│
┌──────▼───────┐
│ API Gateway │
│ /transcribe │
└──────┬───────┘
│
┌──────▼───────┐
│ Hybrid Router │
└──┬───┬───┬───┘
│ │ │
┌───────────┘ │ └───────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Sync │ │ Queue │ │ Callback │
│ (<60s) │ │ (BullMQ) │ │ (>5min) │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└──────────┬───┘──────────────┘
│
┌───────▼──────┐
│ Deepgram │
│ API │
└───────┬──────┘
│
┌───────▼──────┐
│ Results │
│ Store │
└──────────────┘
Output
- Sync REST endpoint for short files
- BullMQ queue with workers for batch processing
- WebSocket proxy for real-time streaming
- Hybrid router with auto-mode selection
- Architecture diagram
Error Handling
| Issue | Cause | Solution |
|---|
| Sync timeout on large file | Wrong pattern selected | Use async queue or callback |
| Queue backlog growing | Workers overloaded | Scale workers, increase concurrency |
| WebSocket disconnects | Network instability | Auto-reconnect with backoff |
| Callback not received | Endpoint unreachable | Check HTTPS, verify callback URL |
Resources