Integrating ElevenLabs

ElevenLabs is an text to speech tool that can generate natural sounding audio from text. It's an excellent tool for dubbing content, creating audiobooks, or even for accessibility purposes.

Integrating ElevenLabs into your Appwrite project is simple. This tutorial will guide you through the process of setting up the ElevenLabs API and integrating it into your Appwrite project.

Prerequisites

1

Create new function

Head to the Appwrite Console then click on Functions in the left sidebar and then click on the Create Function button.

Create function screen

Create function screen

  1. In the Appwrite Console's sidebar, click Functions.

  2. Click Create function.

  3. Under Connect Git repository, select your provider.

  4. After connecting to GitHub, under Quick start, select the Node.js starter template.

  5. In the Variables step, add the ELEVENLABS_API_KEY, generate it here. For the APPWRITE_API_KEY, tick the box to Generate API key on completion.

  6. Follow the step-by-step wizard and create the function.

2

Add dependencies

Once the function is created, navigate to the freshly created repository and clone it to your local machine.

Install the undici package to make requests to the ElevenLabs API and node-appwrite package to upload the generated audio files to Appwrite Storage.

Bash
npm install undici node-appwrite
3

Create utility functions

For this example, the function will be able to take both GET and POST requests.

For the GET request, return a static HTML page that will have a form to submit text to the API. Meanwhile the POST request will send the text to the ElevenLabs API and return the generated audio file.

To begin with write the code to return the static HTML page, to do this create a new src/utils.js file with the following code:

JavaScript
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const staticFolder = path.join(__dirname, '../static');

export function getStaticFile(fileName) {
  return fs.readFileSync(path.join(staticFolder, fileName)).toString();
}
4

Handle GET request

Write the GET request handler in the src/main.js file. This handler will return a static HTML page you'll create later.

JavaScript
import { getStaticFile } from './utils.js';

export default async ({ req, res, error }) => {
  if (req.method === 'GET') {
    return res.text(getStaticFile('index.html'), 200, {
      'Content-Type': 'text/html; charset=utf-8',
    });
  }
};

A check is also included to ensure that the ELEVENLABS_API_KEY, APPWRITE_API_KEY and APPWRITE_BUCKET_ID environment variables is set.

5

Create web page

Create a HTML web page that the function will serve. Create a new file at static/index.html with some HTML boilerplate:

HTML
<!doctype html>
<html lang="en">
</html>

Within the <html> tag, Add a <head> tag that will define the style and scripts.

HTML
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>ElevenLabs Demo</title>

  <script>
    async function onSubmit(prompt) {
      const response = await fetch('/', {
        method: 'POST',
        body: JSON.stringify({ text: prompt }),
        headers: {
          'Content-Type': 'application/json',
        },
      });

      const json = await response.json();

      if (!json.ok || json.error) {
        alert(json.error);
      }

      return json.response;
    }
  </script>

  <script src="//unpkg.com/alpinejs" defer></script>

  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
</head>

And after the </head> tag add this <body> which will contain the actual form:

HTML
<body>
  <main class="main-content">
    <div class="top-cover u-padding-block-end-56">
      <div class="container">
        <div
          class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16"
        >
          <h1 class="heading-level-1">ElevenLabs Demo</h1>
          <code class="u-un-break-text"></code>
        </div>
        <p
          class="body-text-1 u-normal u-margin-block-start-8"
          style="max-width: 50rem"
        >
          Use this page to test your implementation with ElevenLabs. Enter
          text and receive an audio response.
        </p>
      </div>
    </div>
    <div
      class="container u-margin-block-start-negative-56"
      x-data="{ prompt: '', response: '', loading: false }"
    >
      <div class="card u-flex u-gap-24 u-flex-vertical">
        <div class="u-flex u-cross-center u-gap-8">
          <div
            class="input-text-wrapper is-with-end-button u-width-full-line"
          >
            <input x-model="prompt" type="search" placeholder="Enter text" />
            <div class="icon-search" aria-hidden="true"></div>
          </div>

          <button
            class="button"
            x-bind:disabled="loading"
            x-on:click="async () => { loading = true; response = ''; try { response = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }"
          >
            <span class="text">Generate</span>
          </button>
        </div>
        <template x-if="response">
          <div class="u-flex u-flex-vertical u-gap-12">
            <div class="u-flex u-flex-vertical u-gap-12 card">
              <div class="u-flex u-gap-12">
                <h5 class="eyebrow-heading-2">Output:</h5>
              </div>

              <div>
                <audio x-bind:src="response" controls></audio>
              </div>
            </div>
          </div>
        </template>
      </div>
    </div>
  </main>
</body>

All of this together will render a form that will submit your text to the Appwrite function through a POST request which you'll create next. The Appwrite function will call ElevenLabs's API, upload the audio to Appwrite Storage and return the URL, which will be displayed on your page.

6

Handle POST Request

Add methods necessary to integrate with the ElevenLabs API:

Import fetch, and the required features from the Appwrite Node.js SDK at the top of the main.js file

JavaScript
import { Client, Storage, ID, InputFile, Permission, Role } from "node-appwrite";
import { fetch } from "undici";

Next add code to validate the body of the request and initialize the Appwrite SDK:

JavaScript
const client = new Client()
  .setEndpoint(process.env.APPWRITE_ENDPOINT ?? "https://cloud.appwrite.io/v1")
  .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
  .setKey(process.env.APPWRITE_API_KEY);

if (!req.body.text || typeof req.body.text !== "string") {
  return res.json({ ok: false, error: "Missing required field `text`" }, 400);
}

Send a request to the ElevenLabs API and return the response:

JavaScript
const body = {
  accent: req.body.accent || "british",
  accent_strength: 1.0,
  age: req.body.age || "young",
  gender: req.body.gender || "female",
  text: req.body.text,
};

const response = await fetch(
  "https://api.elevenlabs.io/v1/voice-generation/generate-voice",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "xi-api-key": process.env.ELEVENLABS_API_KEY,
    },
    body: JSON.stringify(body),
  },
);

if (response.status !== 200) {
  return res.json({ ok: false, error: "Failed to generate audio" }, 500);
}

This code will send the prompt to the ElevenLabs API and return the audio as a blob, additionally it'll also catch any errors we could encounter and reports them for easy debugging.

7

Store Audio in Appwrite Storage

Store the audio file in Appwrite Storage for easy retrieval later:

JavaScript
const storage = new Storage(client);

const file = await storage.createFile(
  process.env.APPWRITE_BUCKET_ID,
  ID.unique(),
  InputFile.fromBlob(await response.blob(), "audio.mp3"),
  [Permission.read(Role.any())],
);

To show it to the user, parse the download URL from Appwrite and return it in the response:

JavaScript
const url = `${process.env.APPWRITE_ENDPOINT}/storage/buckets/${process.env.APPWRITE_BUCKET_ID}/files/${file.$id}/view?project=${process.env.APPWRITE_FUNCTION_PROJECT_ID}`;

return res.json({ ok: true, response: url });

This should finish up the function, Deploy it to Appwrite by pushing to the git repository created earlier.

8

Test the function

Now that the function is deployed, test it by visiting the function URL in your browser. This should show the UI created earlier and to test it, write a prompt and click the submit button. After a brief moment you should see the audio appear below the input.

Testing the function