> 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.

# Streaming och cachning med Supabase

> **Note**
>
> **Guide** · Förutsätter att du har slutfört [ElevenAPI- snabbstarten](/docs/sv/eleven-api/quickstart) och har ett Supabase-konto.

## Introduktion

I den här guiden lär du dig att bygga ett edge-API för att generera, streama, lagra och cacha tal med Supabase Edge Functions, Supabase Storage och ElevenLabs.

## Krav

* Ett ElevenLabs-konto med en [API-nyckel](https://elevenlabs.io/app/settings/api-keys).
* Ett [Supabase](https://supabase.com)-konto (du kan registrera ett kostnadsfritt konto via [database.new](https://database.new)).
* [Supabase CLI](https://supabase.com/docs/guides/local-development) installerat på din dator.
* [Deno runtime](https://docs.deno.com/runtime/getting_started/installation/) installerat på din dator och valfritt [konfigurerat i din favorit-IDE](https://docs.deno.com/runtime/getting_started/setup_your_environment).

## Konfiguration

### Skapa ett Supabase-projekt lokalt

När du har installerat [Supabase CLI](https://supabase.com/docs/guides/local-development) kör du följande kommando för att skapa ett nytt Supabase-projekt lokalt:

```bash
supabase init
```

### Konfigurera lagrings-bucketen

Du kan konfigurera Supabase CLI så att den automatiskt skapar en lagrings-bucket genom att lägga till följande konfiguration i filen `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**
>
> När du kör `supabase start` skapas en ny lagrings-bucket i ditt lokala Supabase-
> projekt. Om du vill skicka detta till ditt hostade Supabase-projekt kan du köra `supabase seed
>   buckets --linked`.

### Konfigurera bakgrundsaktiviteter för Supabase Edge Functions

För att använda bakgrundsaktiviteter i Supabase Edge Functions vid lokal utveckling behöver du lägga till följande konfiguration i filen `config.toml`:

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

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

> **Note**
>
> När du kör med policyn `per_worker` laddas Function inte om automatiskt vid ändringar. Du måste
> starta om den manuellt genom att köra `supabase functions serve`.

### Skapa en Supabase Edge Function för talgenerering

Skapa en ny Edge Function genom att köra följande kommando:

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

Om du använder VS Code eller Cursor väljer du `y` när CLI frågar "Generate VS Code settings for Deno? \[y/N]"!

### Konfigurera miljövariablerna

Skapa en ny `.env`-fil i katalogen `supabase/functions` och lägg till följande variabler:

**`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
```

### Beroenden

Projektet använder ett par beroenden:

* Biblioteket [@supabase/supabase-js](https://supabase.com/docs/reference/javascript) för att interagera med Supabase-databasen.
* ElevenLabs [JavaScript SDK](/docs/sv/eleven-api/quickstart) för att interagera med text-till-tal-API:et.
* Open source-biblioteket [object-hash](https://www.npmjs.com/package/object-hash) för att generera en hash från förfrågningsparametrarna.

Eftersom Supabase Edge Function använder [Deno runtime](https://deno.land/) behöver du inte installera beroendena. Du kan i stället [importera](https://docs.deno.com/examples/npm/) dem med prefixet `npm:`.

## Koda Supabase Edge Function

Lägg till följande kod i den nyligen skapade filen `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" },
    });
  }
});
```

### Djupdykning i koden

Det finns ett par saker i koden som är värda att notera. Vi går igenom den steg för steg.

#### Hantera den inkommande förfrågan

Använd hanteraren `Deno.serve` för att hantera den inkommande förfrågan. I demon validerar vi inte förfrågans ursprung, men du kan till exempel validera ursprunget eller lägga till en användaråtkomsttoken och validera den med [Supabase Auth](https://supabase.com/docs/guides/functions/auth).

Från den inkommande förfrågan extraherar funktionen parametrarna `text` och `voiceId`. Parametern `voiceId` är valfri och använder som standard ElevenLabs-ID:t för rösten "Allison".

Med biblioteket `object-hash` genererar funktionen en hash från förfrågningsparametrarna. Den här hashen används för att kontrollera om det finns befintliga ljudfiler i 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);

// ...
})
```

#### Kontrollera om det finns en befintlig ljudfil i Supabase Storage

Supabase Storage har ett [smart CDN inbyggt](https://supabase.com/docs/guides/storage/cdn/smart-cdn) som gör det enkelt att cacha och leverera dina filer.

Här kontrollerar funktionen om det finns en befintlig ljudfil i Supabase Storage. Om filen finns returnerar funktionen filen från 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;
}
```

#### Generera tal som en ström och dela upp den i två grenar

Med streamingfunktionerna i ElevenLabs API genererar funktionen en ström. Fördelen är att du kan börja streama ljudet tillbaka till användaren direkt, även för längre text, och sedan ladda upp strömmen till Supabase Storage i bakgrunden.

Det ger bästa möjliga användarupplevelse och gör att även stora textblock känns magiskt snabba. Det sker på rad 17, där metoden `stream.tee()` delar upp läsströmmen i två grenar: en för webbläsaren och en för 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" },
  });
}
```

#### Ladda upp ljudströmmen till Supabase Storage i bakgrunden

Metoden `EdgeRuntime.waitUntil` på rad 20 i föregående steg används för att ladda upp ljudströmmen till Supabase Storage i bakgrunden med funktionen `uploadAudioToStorage`. Det gör att funktionen kan returnera streamingsvaret direkt till webbläsaren medan ljudet laddas upp till Supabase Storage.

När lagringsobjektet har skapats kommer funktionen nästa gång dina användare gör en förfrågan med samma parametrar att returnera ljudfilen från Supabase Storage CDN.

```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 });
}
```

## Kör lokalt

Kör följande kommandon för att köra funktionen lokalt:

```bash
supabase start
```

När den lokala Supabase-stacken är igång kör du följande kommando för att starta funktionen och se loggarna:

```bash
supabase functions serve
```

### Prova själv

Gå till `http://127.0.0.1:54321/functions/v1/text-to-speech?text=hello%20world` för att höra funktionen i praktiken.

Gå sedan till `http://127.0.0.1:54323/project/default/storage/buckets/audio` för att se ljudfilen i din lokala Supabase Storage-bucket.

## Distribuera till Supabase

Om du inte redan har gjort det skapar du ett nytt Supabase-konto på [database.new](https://database.new) och länkar det lokala projektet till ditt Supabase-konto:

```bash
supabase link
```

Kör sedan följande kommando för att distribuera funktionen:

```bash
supabase functions deploy
```

### Ange funktionens hemligheter

Nu när du har angett alla hemligheter lokalt kan du köra följande kommando för att ange hemligheterna i ditt Supabase-projekt:

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

## Testa funktionen

Funktionen är utformad så att den kan användas direkt som källa för ett `<audio>`-element.

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

## Nästa steg

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

Streama ljud stegvis utan Supabase som en enklare startpunkt.

#### [API-referens](/docs/sv/api-reference/text-to-speech/stream)

Fullständig API-referens och parametrar för TTS-streaming.