MiTunes - Music App: Appwrite x Hashnode Hackathon

MiTunes - Music App: Appwrite x Hashnode Hackathon

Harmonizing NestJs and Appwrite to Bring Music to Your Fingertips

We are excited to present our project submission for the Appwrite x Hashnode Hackathon.

Team Details

Meet the innovative team members who created MiTunes

Description of Project

MiTunes is an innovative music streaming platform that empowers users to upload, stream, and share their songs, podcasts, and other audio content with a vibrant community of music lovers.

With MiTunes, users can showcase their creativity and share their original compositions, covers, remixes, and spoken-word content with a global audience. Whether you're a budding musician, a passionate podcaster, or an audio storyteller, MiTunes provides a platform to express yourself and connect with like-minded individuals who appreciate talents.

The platform boasts a sleek and intuitive interface, making it easy for users to navigate and discover new content. Users can stream, upload audio content, and create a playlist of their liked songs. Additionally, MiTunes offers robust search features, ensuring that users can explore a wide range of audio content.

Features of MiTunes

  1. Music Streaming

    With MiTunes you can seamlessly stream a vast library of songs, podcasts and other audio content.

  2. Audio Content Upload

    It empowers users to upload and share their original audio content allowing them to showcase their talent and creativity with a global audience.

  3. Robust Search Functionality

    MiTunes provides a powerful search functionality that allows users to find specific songs, podcasts, or artists quickly and easily.

  4. Playlist of liked and uploaded content

    MiTunes offers a convenient feature for users by automatically creating a playlist of their uploaded and liked content.

Tech Stack

MiTunes was developed using the following cutting-edge technologies

  • NextJs

  • Typescript

  • TailwindCSS

  • Appwrite Cloud

    • Web client SDK

    • Server client SDK - NodeJs

    • Authentication

    • Database

    • Storage

    • Query

  • Vercel

How Appwrite services helped the development of MiTunes

A huge thanks to Appwrite services, It played a major role in the development of MiTunes. It provided us with the perfect backend infrastructure which helped us focus on developing a user-friendly interface. Here are the services that we used.

  • Appwrite Cloud

    Appwrite Cloud provided us with various services, including authentication, database management, and storage. These services eliminated the need for us to set up and maintain our backend infrastructure, reducing the complexity and time required to build and launch MiTunes.

  • Web Client SDK

    We used the web client SDK to handle user authentication, content creation, retrieval, and modification in the front end of MiTunes.

  • Server Client SDK - NodeJs

    With the Appwrite server client SDK, we were able to carry out server operations, render server-side components, and add an extra security layer to MiTunes app.

  • Authentication

    With the help of Appwrite's Authentication API, we quickly and easily integrated a diverse range of authentication options for MiTunes users. Our implementation includes email login, magic link authentication, and Google account sign-in. we also included email verification and password recovery.

  • Database

    The Appwrite Database provided us with perfect data storage and APIs with which we performed CRUD (Create, Read, Update, Delete) operations on the application data.

  • Storages

    Using the Appwrite Storage APIs we efficiently integrated audio file and image upload into MiTunes. The API also provided us with public links to stream the audio and view the images.

  • Queries

    With Appwrite Query, we were able to filter and sort data from the Appwrite Database.

Some Snippets of code showing how we used Appwrite services

libs/configs.ts

export const appwriteConfig = {
    endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT as string,
    project: process.env.NEXT_PUBLIC_APPWRITE_PROJECT as string,
    apiKey: process.env.APPWRITE_API_KEYS as string,
    databaseId: process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID as string,
    songsCollectionId: process.env.NEXT_PUBLIC_APPWRITE_SONGS_COLLECTION_ID as string,
    likedSongsCollectionId: process.env.NEXT_PUBLIC_APPWRITE_LIKEDSONGS_COLLECTION_ID as string,
    songsBucketId: process.env.NEXT_PUBLIC_APPWRITE_SONGS_BUCKET_ID as string,
    imagesBucketId: process.env.NEXT_PUBLIC_APPWRITE_IMAGES_BUCKET_ID as string,
}

libs/appwriteWeb.ts

import { Client, Databases, Account, Storage } from "appwrite";
import { appwriteConfig } from "./configs";

const {endpoint, project} = appwriteConfig;
const appwriteWebClient = new Client()
    .setEndpoint(endpoint)
    .setProject(project)

const appwriteWebClientAccount = new Account(appwriteWebClient);
const appwriteWebClientDatabases = new Databases(appwriteWebClient)
const appwriteWebClientStorage = new Storage(appwriteWebClient)


export  {
    appwriteWebClient, 
    appwriteWebClientAccount, 
    appwriteWebClientDatabases, 
    appwriteWebClientStorage
};

libs/appwriteServer.ts

import { Client, Databases,  } from 'node-appwrite'
import { appwriteConfig } from './configs';

const {endpoint, project, apiKey} = appwriteConfig;
const appwriteServerClient = new Client()
    .setEndpoint(endpoint)
    .setProject(project)
    .setKey(apiKey)

const appwriteServerClientDatabases = new Databases(appwriteServerClient);

export {appwriteServerClient, appwriteServerClientDatabases}

components/SignupModal.ts

Preview of new account sign-up and verification message creation

 import { appwriteWebClientAccount } from "@/libs/appwriteWeb"
 ...
 const [message, setMessage] = useState<Message | null>(null);
 const { user, setCurrentUser} = useUser()
 ... 
 const onSubmit: SubmitHandler<SignupFormValues> = async (values) => {
    ...
    try {
      await appwriteWebClientAccount.create(
        ID.unique(),
        values.email,
        values.password,
        values.fullname
      )
      await appwriteWebClientAccount.createEmailSession(values.email,
        values.password)
      const verificationURL = `${APP_BASE_URL}/verify`;
      await appwriteWebClientAccount.createVerification(verificationURL)
      appwriteWebClientAccount.deleteSession('current');
      setMessage({ success: 'Check your email for the confirmation link' })
      ...
    } catch (error) {
      setMessage({ error: (error as Error)?.message })
    } 
...
  };
...

components/SigninModal.ts

Showing the implementation of account login and verification message creation if the user account is not verified.

 import { appwriteWebClientAccount } from "@/libs/appwriteWeb"
 ...
 const [message, setMessage] = useState<Message | null>(null);
 const { user, setCurrentUser} = useUser()
    ...
 const onSubmit: SubmitHandler<SignInFormValues> = async (values) => {
    ...
   try {
    await appwriteWebClientAccount.createEmailSession(values.email, values.password);
    setCurrentUser();
   } catch (error) {
    ...
    setMessage({ error: (error as Error)?.message })
   }
    ...
  }
const sendVerification  = async () => {
 try {
  const verificationURL = `${APP_BASE_URL}/verify`;
  await appwriteWebClientAccount.createVerification(verificationURL)
  await appwriteWebClientAccount.deleteSession('current');
  setMessage({ success: 'Check your email for the confirmation link' })
 } catch (error) {
  ...
 }
}
useEffect(() => {
    if (user?.isVerified) {
       ...
      router.refresh();
    } else if (user && !user?.isVerified) {
      sendVerification()
    }
}, [...])
...

components/UploadModal.ts

Implementation of audio content and image upload to Appwrite bucket storage and database

import { appwriteWebClientAccount } from "@/libs/appwriteWeb"
import { appwriteConfig } from "@/libs/configs";
 ...
const onSubmit: SubmitHandler<UploadFormValues> = async (values) => {
    try {
      ...
        const { databaseId, songsCollectionId, songsBucketId, imagesBucketId } = appwriteConfig
      const imageFile = values.image?.[0]
      const songFile = values.song?.[0]
      if (!imageFile || !songFile || !user) {
        return
      }
      const uniqueID = uniqid();

      const songUploadResponse = await appwriteWebClientStorage.createFile(songsBucketId, uniqueID, songFile)
      const imageUploadResponse = await appwriteWebClientStorage.createFile(imagesBucketId, uniqueID, imageFile)

      const response = await appwriteWebClientDatabases.createDocument(databaseId, songsCollectionId, ID.unique(), {
        title: values.title,
        author: values.author,
        songPath: getSongURL(songUploadResponse.$id),
        imagePath: getImageURL(imageUploadResponse.$id),
        userId: user.id
      })
      toast.success('Song has been uploaded')
      ...
      router.refresh();
    } catch (error) {
      toast.error('Upload failed: Something went wrong')
    } 
    ....
  }
...

actions/getSongs.ts

Showing how to fetch songs from Appwrite database

import { appwriteServerClientDatabases } from "@/libs/appwriteServer";
import { appwriteConfig } from "@/libs/configs";
...
const getSongs = async (): Promise<Song[]> => {
    const { databaseId, songsCollectionId } = appwriteConfig
    try {
        const response = await appwriteServerClientDatabases.listDocuments(databaseId, songsCollectionId, [Query.orderDesc('$createdAt')])
        if (response.documents.length) {
            const data = response.documents.map((item) => ({ ...item, id: item.$id }))
            return data as any
        }
    } catch (error) {
        console.log(error)
    }
    return [];
}
export default getSongs;

Challenges We Faced and How We Overcame

  1. Understanding and Integrating Appwrite

    This was the major challenge we encountered because we were not familiar with Appwrite before now. We overcame this challenge, by thoroughly studying available resources, including documentation, tutorials, and sample projects.

  2. Meeting our Authentication and Authorization Requirements

    To ensure only verified users could log in to upload, share, and like audio content, we were faced with the challenge of implementing a robust verification system for users. After thoroughly exploring the documentation of Appwrite authentication and gaining deep insights into its functionalities, we were able to build our authentication and authorization algorithm and integrated Appwrite APIs to suit our needs.

  3. Rendering Server Side Components

    We initially built the app using only client-side components by consuming just the Apis form Appwrite web client SDK, But the need of carrying server-side operations and rendering server-side components became a challenge. We tackled this by integrating the Appwrite Node Server Client SDK.

Public Code Repo

https://mi-tunes.vercel.app