Hey!Sound: Upload and Vote for the Best Audios on the Internet
Tags: netlifychallenge, devchallenge, webdev, javascript
Published: 2024-05-13 00:55:58

*This is a submission for the [Netlify Dynamic Site Challenge](https://dev.to/challenges/netlify): 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](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/](https://heysound.netlify.app/)
Source code:[https://github.com/fazzaamiarso/heysound](https://github.com/fazzaamiarso/heysound)

![home page ui](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gfh1sour2zupjvm2padc.png)

## 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
```js
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(),
    },
  });
}

```
Since I display the audios based on categories, I can conveniently filter them when calling the API with prefix
```js
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;
}
```
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

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

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

```

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](https://www.linkedin.com/in/fazzaamiarso/) 😀. I love talking and learning from new people.