> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Streamen en cachen met Supabase

> **Note**
>
> **Handleiding** · Hierbij wordt ervan uitgegaan dat je de [ElevenAPI quickstart](/docs/nl/eleven-api/quickstart) hebt voltooid en een Supabase-account hebt.

## Inleiding

In deze handleiding leer je hoe je een edge API bouwt om spraak te genereren, streamen, opslaan en cachen met Supabase Edge Functions, Supabase Storage en ElevenLabs.

## Vereisten

* Een ElevenLabs-account met een [API-sleutel](https://elevenlabs.io/app/settings/api-keys).
* Een [Supabase](https://supabase.com)-account (je kunt je via [database.new](https://database.new) aanmelden voor een gratis account).
* De [Supabase CLI](https://supabase.com/docs/guides/local-development) geïnstalleerd op je computer.
* De [Deno runtime](https://docs.deno.com/runtime/getting_started/installation/) geïnstalleerd op je computer en optioneel [ingesteld in je favoriete IDE](https://docs.deno.com/runtime/getting_started/setup_your_environment).

## Instellen

### Lokaal een Supabase-project maken

Voer na het installeren van de [Supabase CLI](https://supabase.com/docs/guides/local-development) de volgende opdracht uit om lokaal een nieuw Supabase-project te maken:

```bash
supabase init
```

### De storagebucket configureren

Je kunt de Supabase CLI automatisch een storagebucket laten genereren door deze configuratie toe te voegen aan het bestand `config.toml`:

**`./supabase/config.toml`**

```toml ./supabase/config.toml
[storage.buckets.audio]
public = false
file_size_limit = "50MiB"
allowed_mime_types = ["audio/mp3"]
objects_path = "./audio"
```

> **Note**
>
> Wanneer je `supabase start` uitvoert, wordt hiermee een nieuwe storagebucket gemaakt in je lokale Supabase-
> project. Als je deze wilt pushen naar je gehoste Supabase-project, kun je `supabase seed
>   buckets --linked` uitvoeren.

### Achtergrondtaken voor Supabase Edge Functions configureren

Om achtergrondtaken in Supabase Edge Functions te gebruiken wanneer je lokaal ontwikkelt, moet je de volgende configuratie toevoegen aan het bestand `config.toml`:

**`./supabase/config.toml`**

```toml ./supabase/config.toml
[edge_runtime]
policy = "per_worker"
```

> **Note**
>
> Wanneer je met het beleid `per_worker` werkt, wordt Function niet automatisch opnieuw geladen na wijzigingen. Je moet
> deze handmatig herstarten door `supabase functions serve` uit te voeren.

### Een Supabase Edge Function maken voor het genereren van spraak

Maak een nieuwe Edge Function door de volgende opdracht uit te voeren:

```bash
supabase functions new text-to-speech
```

Als je VS Code of Cursor gebruikt, selecteer je `y` wanneer de CLI vraagt: "Generate VS Code settings for Deno? \[y/N]"!

### De omgevingsvariabelen instellen

Maak in de directory `supabase/functions` een nieuw `.env`-bestand en voeg de volgende variabelen toe:

**`supabase/functions/.env`**

```env supabase/functions/.env
# Find / create an API key at https://elevenlabs.io/app/settings/api-keys
ELEVENLABS_API_KEY=your_api_key
```

### Afhankelijkheden

Het project gebruikt een aantal afhankelijkheden:

* De bibliotheek [@supabase/supabase-js](https://supabase.com/docs/reference/javascript) om met de Supabase-database te werken.
* De ElevenLabs [JavaScript SDK](/docs/nl/eleven-api/quickstart) om met de text-to-speech API te werken.
* De open-sourcebibliotheek [object-hash](https://www.npmjs.com/package/object-hash) om een hash uit de requestparameters te genereren.

Omdat Supabase Edge Function de [Deno runtime](https://deno.land/) gebruikt, hoef je de afhankelijkheden niet te installeren. Je kunt ze via het voorvoegsel `npm:` [importeren](https://docs.deno.com/examples/npm/).

## De Supabase Edge Function coderen

Voeg de volgende code toe aan je nieuw gemaakte bestand `supabase/functions/text-to-speech/index.ts`:

**`supabase/functions/text-to-speech/index.ts`**

```ts supabase/functions/text-to-speech/index.ts
// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "jsr:@supabase/supabase-js@2";
import { ElevenLabsClient } from "npm:elevenlabs";
import * as hash from "npm:object-hash";

const supabase = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);

const elevenlabs = new ElevenLabsClient({
  apiKey: Deno.env.get("ELEVENLABS_API_KEY"),
});

// Upload audio to Supabase Storage in a background task
async function uploadAudioToStorage(stream: ReadableStream, requestHash: string) {
  const { data, error } = await supabase.storage
    .from("audio")
    .upload(`${requestHash}.mp3`, stream, {
      contentType: "audio/mp3",
    });

  console.log("Storage upload result", { data, error });
}

Deno.serve(async (req) => {
  // To secure your function for production, you can for example validate the request origin,
  // or append a user access token and validate it with Supabase Auth.
  console.log("Request origin", req.headers.get("host"));
  const url = new URL(req.url);
  const params = new URLSearchParams(url.search);
  const text = params.get("text");
  const voiceId = params.get("voiceId") ?? "JBFqnCBsd6RMkjVDRZzb";

  const requestHash = hash.MD5({ text, voiceId });
  console.log("Request hash", requestHash);

  // Check storage for existing audio file
  const { data } = await supabase.storage.from("audio").createSignedUrl(`${requestHash}.mp3`, 60);

  if (data) {
    console.log("Audio file found in storage", data);
    const storageRes = await fetch(data.signedUrl);
    if (storageRes.ok) return storageRes;
  }

  if (!text) {
    return new Response(JSON.stringify({ error: "Text parameter is required" }), {
      status: 400,
      headers: { "Content-Type": "application/json" },
    });
  }

  try {
    console.log("ElevenLabs API call");
    const response = await elevenlabs.textToSpeech.stream(voiceId, {
      output_format: "mp3_44100_128",
      model_id: "eleven_multilingual_v2",
      text,
    });

    const stream = new ReadableStream({
      async start(controller) {
        for await (const chunk of response) {
          controller.enqueue(chunk);
        }
        controller.close();
      },
    });

    // Branch stream to Supabase Storage
    const [browserStream, storageStream] = stream.tee();

    // Upload to Supabase Storage in the background
    EdgeRuntime.waitUntil(uploadAudioToStorage(storageStream, requestHash));

    // Return the streaming response immediately
    return new Response(browserStream, {
      headers: {
        "Content-Type": "audio/mpeg",
      },
    });
  } catch (error) {
    console.log("error", { error });
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
});
```

### Verdieping in de code

Er zijn enkele zaken in de code die het vermelden waard zijn. Laten we ze stap voor stap doornemen.

#### Het binnenkomende request afhandelen

Gebruik de handler `Deno.serve` om het binnenkomende request af te handelen. In de demo valideren we de request origin niet, maar je kunt bijvoorbeeld de request origin valideren, of een gebruikerstoegangstoken toevoegen en dit valideren met [Supabase Auth](https://supabase.com/docs/guides/functions/auth).

Uit het binnenkomende request haalt de functie de parameters `text` en `voiceId`. De parameter `voiceId` is optioneel en heeft standaard de ElevenLabs-ID voor de stem "Allison".

Met de bibliotheek `object-hash` genereert de functie een hash uit de requestparameters. Deze hash wordt gebruikt om te controleren op bestaande audiobestanden in Supabase Storage.

```ts {1,5-8}
Deno.serve(async (req) => {
// To secure your function for production, you can for example validate the request origin,
// or append a user access token and validate it with Supabase Auth.
console.log("Request origin", req.headers.get("host"));
const url = new URL(req.url);
const params = new URLSearchParams(url.search);
const text = params.get("text");
const voiceId = params.get("voiceId") ?? "JBFqnCBsd6RMkjVDRZzb";

const requestHash = hash.MD5({ text, voiceId });
console.log("Request hash", requestHash);

// ...
})
```

#### Controleren op bestaand audiobestand in Supabase Storage

Supabase Storage heeft een [slimme CDN ingebouwd](https://supabase.com/docs/guides/storage/cdn/smart-cdn), waarmee je je bestanden eenvoudig kunt cachen en aanbieden.

Hier controleert de functie op een bestaand audiobestand in Supabase Storage. Als het bestand bestaat, retourneert de functie het bestand uit Supabase Storage.

```ts {4,9}
const { data } = await supabase
  .storage
  .from("audio")
  .createSignedUrl(`${requestHash}.mp3`, 60);

if (data) {
  console.log("Audio file found in storage", data);
  const storageRes = await fetch(data.signedUrl);
  if (storageRes.ok) return storageRes;
}
```

#### Spraak als stream genereren en opsplitsen in twee vertakkingen

Met de streamingmogelijkheden van de ElevenLabs API genereert de functie een stream. Het voordeel is dat je, ook bij grotere teksten, de audio onmiddellijk naar je gebruiker kunt streamen en de stream vervolgens op de achtergrond kunt uploaden naar Supabase Storage.

Dit zorgt voor de best mogelijke gebruikerservaring, waardoor zelfs grote tekstblokken magisch snel aanvoelen. Dit gebeurt op regel 17, waar de methode `stream.tee()` de readablestream opsplitst in twee vertakkingen: één voor de browser en één voor Supabase Storage.

```ts {1,17,20,22-27}
try {
  const response = await elevenlabs.textToSpeech.stream(voiceId, {
    output_format: "mp3_44100_128",
    model_id: "eleven_multilingual_v2",
    text,
  });

  const stream = new ReadableStream({
    async start(controller) {
      for await (const chunk of response) {
        controller.enqueue(chunk);
      }
      controller.close();
    },
  });

  // Branch stream to Supabase Storage
  const [browserStream, storageStream] = stream.tee();

  // Upload to Supabase Storage in the background
  EdgeRuntime.waitUntil(uploadAudioToStorage(storageStream, requestHash));

  // Return the streaming response immediately
  return new Response(browserStream, {
    headers: {
      "Content-Type": "audio/mpeg",
    },
  });
} catch (error) {
  console.log("error", { error });
  return new Response(JSON.stringify({ error: error.message }), {
    status: 500,
    headers: { "Content-Type": "application/json" },
  });
}
```

#### De audiostream op de achtergrond uploaden naar Supabase Storage

De methode `EdgeRuntime.waitUntil` op regel 20 in de vorige stap wordt gebruikt om de audiostream op de achtergrond te uploaden naar Supabase Storage met de functie `uploadAudioToStorage`. Hierdoor kan de functie het streamingantwoord onmiddellijk aan de browser retourneren terwijl de audio naar Supabase Storage wordt geüpload.

Zodra het storageobject is gemaakt, retourneert de functie het audiobestand via de Supabase Storage CDN wanneer je gebruikers de volgende keer een request met dezelfde parameters doen.

```ts {2,8-10}
// Upload audio to Supabase Storage in a background task
async function uploadAudioToStorage(
  stream: ReadableStream,
  requestHash: string,
) {
  const { data, error } = await supabase.storage
    .from("audio")
    .upload(`${requestHash}.mp3`, stream, {
      contentType: "audio/mp3",
    });

  console.log("Storage upload result", { data, error });
}
```

## Lokaal uitvoeren

Voer de volgende opdrachten uit om de functie lokaal uit te voeren:

```bash
supabase start
```

Zodra de lokale Supabase-stack actief is, voer je de volgende opdracht uit om de functie te starten en de logs te bekijken:

```bash
supabase functions serve
```

### Probeer het

Ga naar `http://127.0.0.1:54321/functions/v1/text-to-speech?text=hello%20world` om de functie in actie te horen.

Ga daarna naar `http://127.0.0.1:54323/project/default/storage/buckets/audio` om het audiobestand in je lokale Supabase Storage-bucket te zien.

## Deployen naar Supabase

Als je dat nog niet hebt gedaan, maak dan een nieuw Supabase-account aan op [database.new](https://database.new) en koppel het lokale project aan je Supabase-account:

```bash
supabase link
```

Voer daarna de volgende opdracht uit om de functie te deployen:

```bash
supabase functions deploy
```

### De function secrets instellen

Nu je al je secrets lokaal hebt ingesteld, kun je de volgende opdracht uitvoeren om de secrets in je Supabase-project in te stellen:

```bash
supabase secrets set --env-file supabase/functions/.env
```

## De functie testen

De functie is zo ontworpen dat deze rechtstreeks als bron voor een `<audio>`-element kan worden gebruikt.

```html
<audio
  src="https://${SUPABASE_PROJECT_REF}.supabase.co/functions/v1/text-to-speech?text=Hello%2C%20world!&voiceId=JBFqnCBsd6RMkjVDRZzb"
  controls
/>
```

## Volgende stappen

#### [TTS-streaming](/docs/nl/eleven-api/guides/how-to/text-to-speech/streaming)

Stream audio geleidelijk zonder Supabase als eenvoudiger startpunt.

#### [API-referentie](/docs/nl/api-reference/text-to-speech/stream)

Volledige API-referentie en parameters voor TTS-streaming.