music-app-with-auth0-and-cloudinary
Created with CodeSandbox
Since its debut, music streaming has developed dramatically, and it is currently one of the most acceptable methods to listen to music. Many streaming sites let you hear for free, which has reduced the need for piracy while ensuring that artists are compensated fairly. It's also very accessible, with numerous possibilities.
We'll learn how to use Auth0 and Cloudinary to develop a music streaming app in this tutorial.
This project was completed in a Codesandbox. To get started quickly, fork the Codesandbox or run the project.
GitHub Repository:
Created with CodeSandbox
Auth0 is an extensible authentication and authorisation system that is simple to set up. It also provides a complete identity and access management system that works right out of the box, with the ability to customise, expand, and develop new features as needed.
Cloudinary provides a secure and comprehensive API for uploading media files fast and efficiently from the server-side, the browser, or a mobile application. We can upload media assets using Cloudinary's REST API or client libraries (SDKs). These SDKs wrap the upload API and make it easier to integrate with websites and mobile apps.
To create a new project, we use the npx create-react-app
command to scaffold a new project in a directory of our choice.
To install the dependencies we will be using the command below:
cd <project name>
npm install @auth0/auth0-react @supabase/supabase-js bootstrap moment react-audio-player react-bootstrap react-helmet
Once the app is created, and the dependencies are installed, we will see a message with instructions for navigating to our site and running it locally. We do this with the command.
npm start
React.js will start a hot-reloading development environment accessible by default at http://localhost:3000
Kindly visit Auth0 to sign up if you haven’t or login to dashboard, click the Applications
dropdown then application
and finally click the Create Application
button as shown below:
We can now create our application as shown below:
As shown below, we have successfully created our application, but we need to set URLs to point back to our application.
Scroll down to the Application URIs section and set the following
Allowed Callback URLs
= https://de7pd.csb.app
Allowed Logout URLs
= https://de7pd.csb.app
Allowed Web Origins
= https://de7pd.csb.app
Replace https://de7pd.csb.app with our application URL or http://localhost:3000 that we set up earlier. We will be making use of Domain and Client ID in our application later in this tutorial.
Let’s import and set up our application to use the bootstrap dependencies we installed. Navigate to public/index.html
update the file by linking the bootstrap CSS and js with the snippet below:
<!DOCTYPE html>
<html lang="en">
<head>
//...
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css"
integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU"
crossorigin="anonymous"
/>
<title>Music Streaming App</title>
</head>
<body>
//...
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
//...
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<script
src="https://unpkg.com/react/umd/react.production.min.js"
crossorigin
></script>
<script
src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"
crossorigin
></script>
<script
src="https://unpkg.com/react-bootstrap@next/dist/react-bootstrap.min.js"
crossorigin
></script>
</body>
</html>
We require the user interface to stream music on the home page. We will create this by updating the app.js
file to a component. Since it is the React tradition to work with a component structure, we will create a folder called components
in the src
folder and create header.js
, music.js
, and musicList.js
components.
In the components/music.js
file, let us update it with the snippet below:
import ReactAudioPlayer from "react-audio-player";
import moment from "moment";
export default function Music({ musicList, index }) {
return (
<div className="col-md-4">
<div className="card p-3 mb-2" key={index}>
<div className="d-flex justify-content-between">
<div className="d-flex flex-row align-items-center">
<div className="icon">
{" "}
<i className="bx bxl-mailchimp"></i>{" "}
</div>
<div className="ms-2 c-details">
<h6 className="mb-0">{musicList.name}</h6>{" "}
<span>{moment(musicList.created_at).format("MMMM Do YYYY")}</span>
</div>
</div>
<div className="badge">
{" "}
<span role="img" aria-label="">
Hot 🔥
</span>{" "}
</div>
</div>
<div className="mt-2">
<h4 className="heading">{musicList.title}</h4>
<div className="mt-2">
<ReactAudioPlayer src={`${musicList.url}`} controls />
</div>
</div>
</div>
</div>
);
}
In the snippet above, we created a component for a single music card with musicList
and index
props. We also imported ReactAudioPlayer and moment for the audio player and upload date formatting, respectively.
Inside the musicList.js
component, we will update it with the snippet below by importing the music component and iterating through the sample musicList array.
import Music from "./music";
export default function App() {
const musicList = [
{
name: "olanetsoft",
title: "Bang Bang",
url: "https://res.cloudinary.com/demo/video/upload/dog.mp3",
created_at:"2021-10-04T23:30:01.000Z",
}
]
return (
<div className="row">
{musicList.map((m, key) => (
<Music musicList={m} index={key} />
))}
</div>
);
}
Let us update the header.js
component we created earlier with the snippet below:
import { Button } from "react-bootstrap";
export default function Header() {
return (
<div className="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm">
<h5 className="my-0 mr-md-auto font-weight-normal">
Music Streaming App with Auth0 and Cloudinary
</h5>
<nav className="my-2 my-md-0 mr-md-3">
<a className="p-2 text-success" href="/">
Home
</a>
<a className="p-2 text-danger" href="/">
Trending
</a>
<a className="p-2 text-info" href="/">
Top Songs
</a>
</nav>
<Button
id="btnUpload"
className="btn margin"
variant="primary"
>
Upload Song
</Button>
</div>
);
}
We can now update our src/app.js
file as shown below:
import MusicList from "../components/musicList";
import "./styles.css";
export default function App() {
return (
<div className="container mt-5 mb-3">
<Header />
<MusicList />
</div>
);
}
The current user interface doesn’t look aesthetically pleasing, we’ll add some styling with CSS. We will update src/styles.css
file with the following content in this GitHub Gist.
Our application should now look like this on http://localhost:3000/:
We're currently working with sample data, which is not ideal. We should be able to upload and stream songs that others have uploaded.
We'll use Auth0 to track who's uploading new songs, and then we'll use Cloudinary to do the actual uploading before saving it to the database.
Let’s create .env
file in root of our project and populate it with Domain and Client ID from our Auth0 dashboard with the snippet below:
AUTH0_DOMAIN=dev-9hbpo12k.us.auth0.com
AUTH0_CLIENT_ID=tdYpNQ8Qqjymi0dOC7wZdGGWlYCN6FR3
Inside src/index.js
let us import Auth0Provider
and setup our application with the snippet below:
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Auth0Provider } from "@auth0/auth0-react";
const domain = process.env.AUTH0_DOMAIN;
const clientId = process.env.AUTH0_CLIENT_ID;
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<Auth0Provider
domain={domain}
clientId={clientId}
redirectUri={window.location.origin}
>
<App />
</Auth0Provider>
,
</StrictMode>,
rootElement
);
We can now create login-button.js
, logout-button.js
and loading.js
component inside components
folder respectively, using the snippet below:
Inside components/login-button.js
import { useAuth0 } from "@auth0/auth0-react";
import { Button } from "react-bootstrap";
export default function Login() {
const { loginWithRedirect } = useAuth0();
return (
<Button
id="btnLogin"
className="btn margin"
onClick={() => loginWithRedirect()}
variant="primary"
>
Upload Music
</Button>
);
}
components/logout-button.js
import { useAuth0 } from "@auth0/auth0-react";
import { Button } from "react-bootstrap";
export default function Logout() {
const { logout } = useAuth0();
return (
<Button
id="btnLogin"
className="btn margin"
onClick={() => logout()}
variant="danger"
>
Logout
</Button>
);
}
Then inside components/loading.js
import "../src/styles.css";
export default function Loading() {
return <div className="spinner"></div>;
}
We may proceed to import the login
and logout
component inside header.js
file created earlier as shown below:
import { useState, useEffect } from "react";
import { Button } from "react-bootstrap";
import { useAuth0 } from "@auth0/auth0-react";
import Login from "../components/login-button";
import Logout from "../components/logout-button";
export default function Header() {
const { isAuthenticated } = useAuth0();
return (
<div className="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm">
{/* */}
{isAuthenticated ? (
<>
<div>
<Button
id="btnUpload"
className="btn margin"
variant="primary"
>
Upload Song
</Button>
<Logout />
</div>
</>
) : (
<Login />
)}
</div>
);
}
Updating src/app.js
//...
import Loading from "../components/loading";
export default function App() {
const { isLoading } = useAuth0();
if (isLoading) {
return <Loading />;
}
return (
//....
);
}
Let us test our application, and we should have something similar to what we have below after clicking the Upload Song
button.
In the screenshot above, we've successfully logged in, and you'll notice that the UI in the header has changed to include a logout button.
We will be using Cloudinary’s upload widget because of its ability lets us upload media assets from multiple sources including Dropbox, Facebook, Instagram,.
Create a free cloudinary account to obtain your cloud name and upload_preset.
Upload presets allow us to centrally define a set of asset upload choices rather than providing them in each upload call. A Cloudinary cloud name is a unique identifier associated with our Cloudinary account.
First, from a content delivery network (CDN) we will include the Cloudinary widget’s JavaScript file in our index.js located in src/app.
js
. We include this file using the react-helmet’s <Helmet>
component, which lets us add data to the Head portion of our HTML document in React.
//..
import "./styles.css";
import { Helmet } from "react-helmet";
export default function App() {
//...
return (
<div className="container mt-5 mb-3">
<Helmet>
<meta charSet="utf-8" />
<script
src="https://widget.Cloudinary.com/v2.0/global/all.js"
type="text/javascript"
></script>
//...
</div>
);
}
The widget requires our Cloudinary cloud_name
and uploadPreset
. The createWidget()
function creates a new upload widget and on the successful upload of either a video or audio, we assign the public_id of the asset to the relevant state variable.
To get our cloudname
and uploadPreset
we follow the steps below:
The cloud name is obtained from our Cloudinary dashboard as shown below.
An upload preset can be found in the “Upload” tab of our Cloudinary settings page, which we access by clicking on the gear icon in the top right corner of the dashboard page.
We then click on the Upload
tab on the settings page:
We scroll down to the bottom of the page to the upload presets section, where we see our upload preset or the option to create one if we don't have any.
Let us update our components/header.js
with the snippet below:
Let us open our app in the browser and click the Upload Song
button; we should see something like this:
We can further customise the widget with more information in this documentation.
We have successfully configured and setup cloudinary in our application, but we will also integrate a supabase database to save all of the songs that users have uploaded.
let us create client.js
to integrate supabase with the sippet below:
import { createClient } from "@supabase/supabase-js";
const URL = "https://kpriwlucrliyacagwjvk.supabase.co";
const ANNON_PUBLIC_SECRET = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYzMzM2NzU2OSwiZXhwIjoxOTQ4OTQzNgY5fQ.uBBXtyxbwKixUgql4tiYUsqOgSPyB4mLSc2kybqPCPI";
export const supabase = createClient(
URL,
ANNON_PUBLIC_SECRET
);
To get the url
and annon public
key, create a supabase account, start a new project, go to settings
then Api
tab.
We'll create a new table named songs
with columns for url
, name
, and title
by going to the table editor tab on the sidebar. Let’s ensure the column type is text for all the columns created.
After successfully creating our table, let's update the components/header.js
file with the snippet below:
In the preceding code line,
createSong
that connects to the songs table in Supabase
, and then we input our data. createPost
method to save them in the database.Let's update the musicList component to retrieve all uploaded songs with the snippet shown below:
import { useState, useEffect } from "react";
import { supabase } from "../client";
import Music from "./music";
export default function App() {
const [musicList, setMusicList] = useState([]);
useEffect(() => {
fetchSongs();
}, []);
async function fetchSongs() {
const { data } = await supabase.from("songs").select();
setMusicList(data);
}
return (
<div className="row">
{musicList.map((m, key) => (
<Music musicList={m} index={key} />
))}
</div>
);
}
Voila 🥳 We are all set; we can now successfully upload songs, stream songs, etc.
This article explains how to use Auth0 and Cloudinary to build a music streaming app utilising Cloudinary's widget capability.
Content created for the Hackmamba Jamstack Content Hackathon using Auth0 and Cloudinary