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
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(),
},
});
}
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;
}
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",
},
});
}
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!` });
}
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.
Top comments (0)