Open In App

Music Streaming Platform using MERN Stack

In this tutorial, we'll walk through the process of creating a music streaming platform using the MERN stack. This project will allow users to browse playlists, view songs within each playlist, search for specific songs, and play them. We'll cover the basics of setting up a MongoDB database, creating RESTful APIs with Node.js and Express, and building a frontend interface using React.

Output Preview: Let us have a look at how the final output will look like.

Screenshot-2024-03-16-223452

Prerequisites:

Approach to create Music Streaming Platform:

Steps to Create the Project:

Step 1: Create a Node.js backend with Express.js to handle API requests and interact with the database.

mkdir music-streaming-backend
cd music-streaming-backend
npm init -y

Step 2: Make server.js to create routes and models:-

Step 3: Build a React frontend :-

npx create-react-app music-streaming-frontend
cd music-streaming-frontend

Step 4: Create interface for browsing playlists, viewing songs, and searching for songs. (PlayList.js, Search.js):-

Step 5: Add style.css to add interaction and formatting:

Frontend Project Structure:

Screenshot-2024-03-07-125240

Backend Project Structure:Screenshot-2024-03-07-125215

Frontend: The updated dependencies in package.json file will look like:

"dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.6.7",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.22.2",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
}

Backend: The updated dependencies in package.json file will look like:

"dependencies": {
    "body-parser": "^1.20.2",
    "cors": "^2.8.5",
    "dotenv": "^16.4.5",
    "express": "^4.18.3",
    "mongoose": "^8.2.1"
}

Example: Code example for the backend:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const bodyParser = require('body-parser');
require('dotenv').config();

const app = express();

// Middleware
app.use(cors());
app.use(bodyParser.json());

// MongoDB Connection
mongoose.connect('mongodb://localhost:27017/musicstreamingdatabase', {
    useNewUrlParser: true,
    useUnifiedTopology: true
})
    .then(() => console.log('Connected to MongoDB'))
    .catch(err => console.error('Failed to connect to MongoDB', err));

// Playlist Model
// Playlist Model
const Playlist =
    mongoose.model('Playlist', new mongoose.Schema({
        name: { type: String, required: true },
        playlistCode:
        {
            type: String,
            required: true,
            unique: true
        }, // New field
        songs:
            [{ type: String }],
        // Store song titles directly
        user:
        {
            type: mongoose.Schema.Types.ObjectId,
            ref: 'User'
        },
        collaborators:
            [
                {
                    type: mongoose.Schema.Types.ObjectId,
                    ref: 'User'
                }]
    }));



// Song Model
// Song Model
const Song = mongoose.model('Song', new mongoose.Schema({
    title: { type: String, required: true },
    artist: { type: String, required: true },
    songcode: { type: String, required: true, unique: true },
    album: String,
    duration: { type: Number, required: true },
    audioUrl: { type: String, required: true }
    // Add audioUrl field for storing song file URL
}));


// Routes
// Playlists
app.get('/api/playlists', async (req, res) => {
    try {
        const playlists =
            await Playlist.find().populate('songs');
        res.json(playlists);
    } catch (err) {
        console.error(err);
        res.status(500).json({ error: 'Server error' });
    }
});

app.get('/api/playlists/:playlistName/songs', async (req, res) => {
    try {
        const playlistName =
            req.params.playlistName;
        const playlist =
            await Playlist.findOne({ name: playlistName }).populate('songs');
        if (!playlist) {
            return res.status(404).json({ error: 'Playlist not found' });
        }
        res.json(playlist.songs);
    } catch (err) {
        console.error(err);
        res.status(500).json({ error: 'Server error' });
    }
});




app.post('/api/playlists', async (req, res) => {
    try {
        const { name, songs, user } = req.body;
        const playlist =
            new Playlist({ name, songs, user });
        await playlist.save();
        res.json(playlist);
    } catch (err) {
        console.error(err);
        res.status(500)
            .json({ error: 'Server error' });
    }
});

// Collaborative Playlists
app.post(
    '/api/playlists/:playlistId/collaborators', async (req, res) => {
        try {
            const { userId } = req.body;
            const playlist = await Playlist.findByIdAndUpdate(
                req.params.playlistId,
                { $addToSet: { collaborators: userId } },
                { new: true }
            );
            res.json(playlist);
        } catch (err) {
            console.error(err);
            res.status(500).json({ error: 'Server error' });
        }
    });

app.get('/api/playlists/collaborative/:userId', async (req, res) => {
    try {
        const playlists =
            await Playlist.find(
                {
                    collaborators: req.params.userId
                });
        res.json(playlists);
    } catch (err) {
        console.error(err);
        res.status(500).json({ error: 'Server error' });
    }
});

// Songs
app.get('/api/songs', async (req, res) => {
    try {
        const { title, artist, album } = req.query;
        const filter = {};
        if (title) filter.title = new RegExp(title, 'i');
        if (artist) filter.artist = new RegExp(artist, 'i');
        if (album) filter.album = new RegExp(album, 'i');

        const songs =
            await Song.find(filter)
                .select('title artist album duration audioUrl');
        // Include audioUrl field
        res.json(songs);
    } catch (err) {
        console.error(err);
        res.status(500).json({ error: 'Server error' });
    }
});


app.post('/api/songs', async (req, res) => {
    try {
        const
            {
                title,
                artist,
                album,
                duration
            } = req.body;
        const song =
            new Song({ title, artist, album, duration });
        await song.save();
        res.json(song);
    } catch (err) {
        console.error(err);
        res.status(500).json({ error: 'Server error' });
    }
});

// Error handling middleware
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500)
        .send('Something went wrong!');
});

// Start server
const PORT = process.env.PORT || 5000;
app.listen(PORT,
    () =>
        console.log(`Server running on port ${PORT}`)
);

Example: Code example for the frontend:

.playlist-container {
    background-color: #eeeeee;
    padding: 20px;
}

.playlist-heading {
    color: #4ecca3;
}

.all-songs {
    display: flex;
    list-style-type: none;
    flex-direction: column;
}

.all-song {
    margin-bottom: 10px;
    padding: 10px;
    background-color: #ffffff;
    border-radius: 5px;
}

.playlist-list {
    list-style-type: none;
    padding: 0;
}

.playlist-item {
    margin-bottom: 10px;
    padding: 10px;
    background-color: #ffffff;
    border-radius: 5px;
}

.search-container {
    background-color: #eeeeee;
    padding: 20px;
}

.search-heading {
    color: #4ecca3;
}

.search-input {
    margin-right: 10px;
    padding: 5px;
}

.search-button {
    padding: 5px 10px;
    background-color: #4ecca3;
    border: none;
    color: #ffffff;
    cursor: pointer;
}

.search-results {
    list-style-type: none;
    padding: 0;
}

.search-item {
    margin-bottom: 10px;
    padding: 10px;
    background-color: #ffffff;
    border-radius: 5px;
}
import React from 'react';
import {
    BrowserRouter as Router,
    Route, Routes, Link
} from 'react-router-dom';
import Playlist from './Playlist';
import Search from './Search';

function App() {
    return (
        <>
            <Playlist />
            <Search />
        </>
    );
}

export default App;
import React,
{
    useEffect,
    useState
} from 'react';
import axios from 'axios';
import './App.css';

const Playlist = () => {
    const [playlists, setPlaylists] = useState([]);
    const [selectedPlaylist, setSelectedPlaylist] = useState(null);
    const [playlistSongs, setPlaylistSongs] = useState([]);


    useEffect(() => {
        axios
            .get("http://localhost:5000/api/playlists")
            .then((res) => {
                setPlaylists(res.data);
            })
            .catch((err) => {
                console.error("Error fetching playlists:", err);
            });
    }, []);

    const fetchSongDetails = async (playlistSongs) => {
        try {
            const response =
                await axios.get("http://localhost:5000/api/songs");
            const allSongs = response.data;
            const matchingSongs =
                playlistSongs.map((playlistSong) => {
                    return allSongs.find(
                        (song) =>
                            song.title === playlistSong);
                });
            return matchingSongs.filter(Boolean); 
            // Filter out undefined/null values
        } catch (error) {
            console.error("Error fetching song details:", error);
            return [];
        }
    };

    const handlePlaylistClick = async (playlistName) => {
        try {
            const response = await axios.get(
                `http://localhost:5000/api/playlists/${encodeURIComponent(
                    playlistName
                )}/songs`
            );
            const songs = response.data;
            const matchingSongs =
                await fetchSongDetails(songs);
            setPlaylistSongs(matchingSongs);
            setSelectedPlaylist(playlistName);
        } catch (error) {
            console.error("Error fetching playlist songs:", error);
        }
    };




    return (
        <div className="playlist-container">
            <h1 className="playlist-heading">Playlists</h1>
            <ul className="playlist-list">
                {
                    playlists.map((playlist, index) => (
                        <li
                            key={index}
                            className="playlist-item"
                            onClick={
                                () =>
                                    handlePlaylistClick(playlist.name)
                            }>
                            {playlist.name}
                        </li>
                    ))
                }
            </ul>
            {selectedPlaylist && (
                <div className="playlist-songs">
                    <h3>Songs in {selectedPlaylist}:</h3>
                    <ul>
                        {
                            playlistSongs.map((song, index) => (
                                <li key={index}>
                                    {song.title} - {song.artist}
                                </li>
                            ))
                        }
                    </ul>
                </div>
            )}
        </div>
    );
};

export default Playlist;
import React,
{
    useState,
    useEffect
} from 'react';
import axios from 'axios';
import './App.css';

const Search = () => {
    const [searchTerm, setSearchTerm] = useState('');
    const [searchResults, setSearchResults] = useState([]);
    const [allSongs, setAllSongs] = useState([]);
    const [audioSrc, setAudioSrc] = useState('');
    const [audioTitle, setAudioTitle] = useState('');
    const [isPlaying, setIsPlaying] = useState(false);

    useEffect(() => {
        const fetchAllSongs = async () => {
            try {
                const response =
                    await axios.get("http://localhost:5000/api/songs");
                setAllSongs(response.data);
            } catch (error) {
                console.error("Error fetching all songs:", error);
            }
        };
        fetchAllSongs();
    }, []);

    const handleSearch = () => {
        axios.get(`http://localhost:5000/api/songs?title=${searchTerm}`)
            .then(res => {
                setSearchResults(res.data);
            })
            .catch(err => {
                console.error('Error searching for songs:', err);
            });
    };

    const handleSongClick = (song) => () => {
        setAudioSrc(song.audioUrl);
        setAudioTitle(song.title);
        setIsPlaying(true);
    };

    const handleAudioEnded = () => {
        setIsPlaying(false);
    };

    const handlePauseClick = () => {
        setIsPlaying(false);
    };

    return (
        <div className="search-container">
            <h1 className="search-heading">Search</h1>
            <input
                type="text"
                className="search-input"
                value={searchTerm}
                onChange={
                    e =>
                        setSearchTerm(e.target.value)
                } />
            <button className="search-button"
                onClick={handleSearch}>
                Search
            </button>
            <ul className="search-results">
                {searchResults.map(song => (
                    <li key={song._id} className="search-item">
                        {song.title} - {song.artist}
                    </li>
                ))}
            </ul>
            <h1>All Songs</h1>
            {audioSrc && (
                <div>
                    <h2>Now Playing: {audioTitle}</h2>
                    <audio src={audioSrc}
                        autoPlay={isPlaying}
                        onEnded={handleAudioEnded} />
                    {
                        isPlaying ?
                            (<button onClick={handlePauseClick}
                                className='search-button'>Pause</button>) :
                            (<button onClick={
                                () => setIsPlaying(true)
                            }
                                className='search-button'>Play</button>)
                    }
                </div>
            )}
            <div className="all-songs">
                {allSongs.map(song => (
                    <div key={song._id}
                        className="all-song"
                        onClick={handleSongClick(song)}>
                        {song.title} - {song.artist}
                    </div>
                ))}
            </div>
        </div>
    );
};

export default Search;

Steps to Run the Application:

Frontend:

npm start

Backend:

npm run dev

Output:

5689-ezgifcom-video-to-gif-converter

Article Tags :