Hey!Sound: Upload and Vote for the Best Audios on the Internet

fazzaamiarso

Fazza Razaq Amiarso

Posted on May 13, 2024

Hey!Sound: Upload and Vote for the Best Audios on the Internet

This is a submission for the Netlify Dynamic Site Challenge: Build with Blobs.

What I Built

An audio sharing app where users can upload their audio with maximum time limit of 30 seconds. Users can tag their audios into 3 categories: Podcast, Music, and Bites. Users can also vote or downvote an uploaded audio (without authentication).

Huge Credits to 50hacks.co for the app's inspiration.

Initially, I got delayed by one whole day because of production build that keep failing when using blobs. However, I'm really grateful that I didn't give up since I ended up learning a lot of interesting things while searching and hacking for the solutions 😉.

Useful Links

Demo: https://heysound.netlify.app/
Source code:https://github.com/fazzaamiarso/heysound

home page ui

Platform Primitives

Filtering Blobs Metadata

The main feature of this app is sharing audio, so I utilize Netlify blobs to store audio with it's metadata to display in UI.

Since netlify blobs have filter by prefix to get blobs list, I used it to categorize the audios by prefixing it with <category>:<key>.

Here's the upload implementation

const store = () => getStore("sounds");

export async function uploadAudio(formData) {
  const audio = formData.get("sound");
  const category = formData.get("category");
  const description = formData.get("description");

  const key = `${category}:${nanoid()}`;

  if (!audio) return { error: "Audio not found" };

  await store().set(key, audio, {
    metadata: {
      category,
      description,
      type: audio.type,
      name: audio.name,
      createdAt: new Date().toISOString(),
    },
  });
}

Enter fullscreen mode Exit fullscreen mode

Since I display the audios based on categories, I can conveniently filter them when calling the API with prefix

const store = () => getStore("sounds");

export async function getSoundsMetadata(prefixFilter) {
  const blobList = await store().list({ prefix: prefixFilter });

  const soundsData = await Promise.all(
    blobList.blobs.map(async (blob) => {
      const metadata = await store().getMetadata(blob.key);
      return { key: blob.key, metadata: metadata?.metadata };
    }),
  );

  return soundsData;
}
Enter fullscreen mode Exit fullscreen mode

Another convenient thing is that, I have the options to only retrieve the metadata so I don't need to pass around blobs in the components (which isn't possible, right?).

Retrieving Audios Blob

To retrieve a blob is pretty simple and straight-forward.Although, I know there are better ways to send the blob back to client. Due to time constraints, I just use the simplest method

const store = () => getStore("sounds");

export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const key = searchParams.get("key");

  if (!key) throw new Error("Key not found!");

  const audioBlob = await store().get(key, {
    type: "stream",
    consistency: "strong",
  });

  return new NextResponse(audioBlob, {
    headers: {
      "Netlify-CDN-Cache-Control": "public, max-age=604800, immutable",
      "Netlify-Vary": "query",
    },
  });
}

Enter fullscreen mode Exit fullscreen mode

Store Voting Data

The final way I use the blob storage is to store voting data. What I like the most is that the data is unstructured, that means I have a lot flexibility on how I want to store my data without strict constraints.

Here's the implementation

const store = () => getStore("votes");

const createVoteKeys = (key) => `${key}:votes`;

export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const key = searchParams.get("key");

  if (!key) return NextResponse.json({ count: 0 });

  const count = await store().get(createVoteKeys(key), { type: "json" });

  return NextResponse.json({ count: count ?? 0 });
}

export async function POST(request) {
  const data = await request.json();

  const { key, action } = data;

  const count = await store().get(createVoteKeys(key), { type: "json" });

  const newCount = count + votesAction

  await store().setJSON(createVoteKeys(key), newCount);

  return NextResponse.json({ message: `Successfully updated: ${key} count!` });
}

Enter fullscreen mode Exit fullscreen mode

If you come until this far, I thank you for your time! I hope by reading this article, someone can find it useful.

Let's chat and connect on Linkedin 😀. I love talking and learning from new people.

💖 💪 🙅 🚩
fazzaamiarso
Fazza Razaq Amiarso

Posted on May 13, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related