Build a podcast platform in Nuxt.js using Appwrite

Tosin Moronfolu - Jan 16 '23 - - Dev Community

Podcasts have become an increasingly popular form of media in recent years, with a wide range of topics and genres available for listeners to choose from. Suppose you are passionate about podcasting and want to create a platform to host and distribute your own shows. In that case, Nuxt.js and Appwrite can be powerful for building a feature-rich and scalable podcasting platform.

This article explores how to build a podcast platform using Nuxt.js and Appwrite, including how to set up the project, upload a podcast, and create a podcast player.

Repository

https://github.com/folucode/appwrite-podcast-app

Prerequisites

  • Basic understanding of CSS, JavaScript, and Nuxt.js.
  • Docker Desktop installed on your computer (run the docker -v command to verify the installation); if you don’t yet have it, install it from here.
  • An Appwrite instance running on your computer. Check out this post to create a local Appwrite instance. It can also be installed using digital ocean or gitpod.

Setting up the project

Create a new Nuxt.js project using the following command:

npx create-nuxt-app my-podcast-platform
Enter fullscreen mode Exit fullscreen mode

This will guide you through several prompts to set up the project, including selecting a package manager, a UI framework, and additional features. Make sure to choose Axios as the HTTP client, as you'll use it to make API calls to Appwrite later.

Once you have set up the project, you can navigate to the project directory and start the development server using the following commands:

cd my-podcast-platform
npm run dev
Enter fullscreen mode Exit fullscreen mode

Setting up the Appwrite database

You'll be using Appwrite's storage and database service in this app. The Appwrite storage bucket is where the Appwrite stores uploaded files.

To set up an Appwrite storage bucket, log in to the Appwrite console and create a new project. Next, click on Storage and then Create bucket. You can name the bucket Podcasts.

create new storage bucket

Next, click on the newly created bucket, go to the settings tab, and then scroll down to Update permissions. Add a new role for Users and give it role all permissions.

create roles

After adding the roles, the next thing to do is to create a database to store all the podcast data.

Click on the Databases tab, and then Create database. You can give it the name of podcasts as well.

create database

Click on the podcasts database and create a new collection in the database. Collections serve as database tables in Appwrite.

You can call it podcast_details, as it stores all the details of your podcast. Next, click on the collection and add new attributes. Attributes serve as database columns in Appwrite.

appwrite

The attributes you’ll add are:

  • fileID of type string
  • title of type string
  • name of type string
  • date of type integer

Also, as you did for the bucket in storage, add a Users role with all permissions for the podcast_details collection.

Setting up the UI

In the components folder, create a file called Podcast.vue and paste the code below into it:

    <template>
      <div class="pod">
        <h3>{{ title }}</h3>
        <div class="pod-details">
          <span>By {{ name }}</span> /
          <span>{{ date }}</span>
          <audio controls>
            <source :src="source" type="audio/mpeg" />
            Your browser does not support the audio element.
          </audio>
        </div>
      </div>
    </template>
    <script>
    export default {
      name: "Podcast",
      props: {
        title: String,
        name: String,
        date: String,
        source: URL,
      },
    };
    </script>
    <style>
    h3 {
      font-weight: bolder;
    }
    .pod {
      background-color: #45a049;
      border-radius: 5px;
      padding: 10px;
      margin: 5px;
    }
    .pod-details {
      background-color: rgb(255, 255, 255);
      display: block;
      padding: 5px;
      margin-bottom: 15px;
      border-radius: 10px;
    }
    span {
      margin-left: 5px;
      margin-right: 5px;
      color: rgb(0, 0, 0);
    }
    audio {
      margin-top: 15px;
      display: block;
    }
    </style>
Enter fullscreen mode Exit fullscreen mode

This component is a simple card that contains all the details of a podcast. The audio file, name, title of the podcast, and date. There's also a props object for each of these attributes.

Next, in the pages/index.vue file, paste the code below:

    <template>
      <div class="container">
        <div class="child">
          <Podcast />
        </div>
        <div class="child">
          <form>
            <label for="title">Title</label>
            <input
              type="text"
              name="title"
              placeholder="Podcast title..."
              v-model="title"
            />
            <label for="country">File</label>
            <input id="file" type="file" />
            <button type="submit" value="Submit">
              Upload
            </button>
          </form>
        </div>
      </div>
    </template>
    <script>
    export default {
      name: "IndexPage",
    };
    </script>
    <style>
    .container {
      border: 3px solid #fff;
      padding: 20px;
    }
    input[type="text"] {
      width: 100%;
      padding: 12px 20px;
      margin: 8px 0;
      display: inline-block;
      border: 1px solid #ccc;
      border-radius: 4px;
      box-sizing: border-box;
    }
    input[type="file"] {
      width: 93%;
      background-color: #ffffff;
      color: rgb(0, 0, 0);
      padding: 14px 20px;
      margin: 8px 0;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    button {
      background-color: #45a049;
      color: white;
      padding: 14px 20px;
      margin: 8px 0;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      width: 100%;
    }
    .child {
      width: 45%;
      float: left;
      padding: 20px;
      border: 2px solid red;
      margin: 5px;
    }
    </style>
Enter fullscreen mode Exit fullscreen mode

In this file, you have the Podcast component and a form to upload a podcast.

Building the app

Before you continue, install these two packages: the Appwrite official package and faker.js, a library to generate mock data.

npm i appwrite @faker-js/faker
Enter fullscreen mode Exit fullscreen mode

Connecting to Appwrite

In the index.vue file, import the Appwrite package and the faker.js package, and then create an Appwrite connection like so:

    ...
    <script>
    import { Client, Account, Databases, Storage, Query } from "appwrite";
    import { faker } from "@faker-js/faker";

    const client = new Client();

    client
      .setEndpoint("http://localhost/v1") // The Appwrite Endpoint
      .setProject("[YOUR-PROJECT-ID]");

    const account = new Account(client);
    const database = new Databases(client);
    const storage = new Storage(client);

    account.createAnonymousSession().then(
      (response) => {
        console.log(response);
      },
      (error) => {
        console.log(error);
      }
    );

    export default {
      name: "IndexPage",
      data() {
        return {
          podcasts: [],
          title: "",
        };
      },
      mounted() {},
    };
    </script>
    ...
Enter fullscreen mode Exit fullscreen mode

In this file, you create an Appwrite instance with an account, database, and storage, which you'll use later in the app. Also, you set up an anonymous session - this saves us the time of making a whole authentication system.

There are two data variables, podcasts, and title.

In the mounted method, check whether the anonymous account is active and subscribe to the documents channel. Subscribing to a channel updates your app in real time once a change is made in the database.

    ...
     mounted() {
        if (account.get !== null) {
          try {
            client.subscribe("documents", (response) => {
            });
          } catch (error) {
            console.log(error, "error");
          }
        }
      }
    ...
Enter fullscreen mode Exit fullscreen mode

Uploading a podcast

To upload a podcast, you'll follow the steps below:

  1. First, upload the audio file to the storage bucket
  2. Retrieve the ID and then store it in the podcast_details collection, along with the name, title, and date
    ...
    methods: {
      async uploadPodcast() {
        const file = await storage.createFile(
          "[YOUR-BUCKET-ID]",
          "unique()",
          document.getElementById("file").files[0]
        );

        await database.createDocument(
          "[YOUR-DATABASE-ID]",
          "[YOUR-COLLECTION-ID]",
          "unique()",
          {
            fileID: file.$id,
            name: faker.name.fullName(),
            title: this.title,
            date: Date.now(),
          }
        );
      },
    },
    ...
Enter fullscreen mode Exit fullscreen mode

Make sure to add a click event to the Upload button and also prevent the page from reloading, like so:

    <button type="submit" value="Submit" @click.prevent="uploadPodcast">
      Upload
    </button>
Enter fullscreen mode Exit fullscreen mode

Fetching the podcasts

To create a function to fetch the podcasts, follow the steps below:

  1. Fetch all the files in the storage bucket
  2. For each audio file, get its details from the podcast_details collection using the fileID
  3. Put all the details in an object and push it to the podcasts array
    ...
    async getPodcasts() {
      const result = await storage.listFiles("63a4866686e5064b953d");

      let podcasts = [];

      result.files.map(async (file) => {
        let link = await storage.getFileView("63a4866686e5064b953d", file.$id);

        let podcastData = await database.listDocuments(
          "63a486f99305afe71e48",
          "63a487ea6bb5ba002a8c",
          [Query.equal("fileID", file.$id)]
        );

        podcasts.push({
          id: file.$id,
          link,
          title: podcastData.documents[0].title,
          date: new Date(podcastData.documents[0].date).toDateString(),
          name: podcastData.documents[0].name,
        });
      });

      this.podcasts = podcasts;
    },
    ...
Enter fullscreen mode Exit fullscreen mode

Every time the page reloads or changes occur in the database, you want to fetch all the podcasts in real time. To do that, call the getPodcasts function on the mounted method like so:

    ...
    mounted() {
      this.getPodcasts();

      if (account.get !== null) {
        try {
          client.subscribe("documents", (response) => {
            this.getPodcasts();
          });
        } catch (error) {
          console.log(error, "error");
        }
      }
    },
    ...
Enter fullscreen mode Exit fullscreen mode

Displaying the podcasts

To display the podcasts, all you have to do is loop over the podcasts array and pass in all the data as props like so:

    ...
    <Podcast
      v-for="podcast in podcasts"
      :key="podcast.id"
      :title="podcast.title"
      :date="podcast.date"
      :source="podcast.link"
      :name="podcast.name"
    />
    ...
Enter fullscreen mode Exit fullscreen mode

You should see the podcasts show up like this:

podcast app view

You can get free audio files from this website: https://mixkit.co/free-stock-music/

Conclusion

In this article, you learned how to build a podcast platform using Nuxt.js and Appwrite. Combining these two technologies helped you to quickly and easily build a feature-rich podcast platform that meets the needs of both content creators and listeners.

Resources

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .