Open In App

Music Discovery App with MERN Stack

In today's digital age, music discovery has become an integral part of our lives. With the rise of streaming platforms and personalized recommendations, users are constantly seeking new music experiences. In this article, we'll delve into the creation of a Music Discovery App using the MERN stack (MongoDB, Express.js, React.js, Node.js). This comprehensive guide will walk you through the process of building a feature-rich web application for exploring and discovering music.

Output Preview:

imresizer-1713878554539

Prerequisites

Approach

Steps to Setup The Backend

Step 1: Create a new directory for the backend and navigate to the backend directory

mkdir music-backend
cd music-backend

Step 2: Initialize a new Node.js project

npm init -y

Step 3: Install the necessary packages/libraries in your project using the following commands.

npm install express mongoose dotenv cors

Project Structure:

Screenshot-2024-04-20-183234

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

  "dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"express": "^4.18.3",
"mongoose": "^8.2.2"
}

Example: Implementation to setup the frontend for the music discovery app.

// server.js

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 3001;

app.use(express.json());
app.use(cors());

// MongoDB connection
mongoose.connect("mongodb://localhost:27017/musicdiscovery",
    {
        useNewUrlParser: true,
        useUnifiedTopology: true
    });
const db = mongoose.connection;
db.on('error',
    console.error.bind(
        console, 'MongoDB connection error:'));
db.once('open',
    () => console.log('Connected to MongoDB'));

// Music model
const musicSchema = new mongoose.Schema({
    title: String,
    artist: String,
    genre: String,
    releaseDate: Date,
    songUrl: String, // Added songUrl field
});

const Music = mongoose.model('Music', musicSchema);

// Routes
app.get('/api/music', async (req, res) => {
    try {
        const { search } = req.query;
        let query = {};
        if (search) {
            query = {
                $or: [
                    // Case-insensitive search for title
                    { title: { $regex: search, $options: 'i' } },
                    // Case-insensitive search for artist
                    { artist: { $regex: search, $options: 'i' } },
                    // Case-insensitive search for genre
                    { genre: { $regex: search, $options: 'i' } },
                ],
            };
        }
        const music = await Music.find(query);
        res.json(music);
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

app.post('/api/music', async (req, res) => {
    try {
        const {
            title, artist, genre,
            releaseDate, songUrl } = req.body;
        const newMusic =
            new Music({
                title, artist, genre,
                releaseDate, songUrl
            });
        await newMusic.save();
        res.status(201).json(newMusic);
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

// Edit music endpoint
app.put('/api/music/:id', async (req, res) => {
    try {
        const { id } = req.params;
        const {
            title, artist, genre,
            releaseDate, songUrl
        } = req.body;
        const updatedMusic =
            await Music.findByIdAndUpdate(
                id, {
                title, artist, genre,
                releaseDate, songUrl
            }, { new: true });
        res.json(updatedMusic);
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

// Delete music endpoint
app.delete('/api/music/:id', async (req, res) => {
    try {
        const { id } = req.params;
        await Music.findByIdAndDelete(id);
        res.json({ message: 'Music deleted successfully.' });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

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

Step to Run Application: Run the application using the following command from the root directory of the project

node server.js

Steps to Setup The Frontend

Step 1: Run the command to create a new React app using create-react-app:

npx create-react-app music-discovery-app

Step 2: Navigate to App Directory:

cd music-discovery-app

Step 3: Install the necessary packages/libraries in your project using the following commands.

npm install axios

Project Structure:

Screenshot-2024-04-20-183305

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.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}

Example: Implementation to setup the frontend for the music discovery app.

/* App.css */

body {
    font-family: Arial, sans-serif;
    background: linear-gradient(
179.4deg, rgb(12, 20, 69) -16.9%, rgb(71, 30, 84) 119.9%);
    color: #ffffff;
    margin: 2%
}

h1 {
    text-align: center;
    margin-bottom: 20px;
}

form {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    margin-bottom: 20px;
}

input {
    padding: 8px;
    margin: 4px;
    border: 1px solid #ffffff;
    border-radius: 4px;
    background-color: rgba(244, 239, 239, 0.1);
    color: #000000;
}

button {
    padding: 8px 16px;
    margin: 4px;
    border: none;
    border-radius: 4px;
    background-color: #ffffff;
    color: #2c5d63;
    cursor: pointer;
}

.music-item {
    border: 1px solid #ccc;
    border-radius: 5px;
    margin-bottom: 10px;
    padding: 10px;
}

.playing {
    background: linear-gradient(
to right, rgb(173, 83, 137), rgb(60, 16, 83)) !important;
    /* Light blue background color for playing song */
}


ul {
    list-style-type: none;
    padding: 0;
}

li {
    margin-bottom: 8px;
}

li strong {
    font-weight: bold;
}

.music-list {
    display: flex;
    justify-content: center;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 20px;
    margin-top: 20px;
}

.music-item {
    width: 600px;
    border: 1px solid #ffffff;
    border-radius: 8px;
    padding: 20px;
    background-color: rgba(255, 255, 255, 0.1);
}

input {
    color: white;
}

.music-info h3 {
    margin-bottom: 10px;
}

.music-info p {
    margin: 5px 0;
}

.search-container {
    margin-top: 20px;
    display: flex;
    justify-content: center;
    align-items: center;
}

.search-container input[type="text"] {
    padding: 8px;
    margin-right: 10px;
    border: 1px solid #ffffff;
    border-radius: 4px;
    background-color: rgba(255, 255, 255, 0.1);
    color: #ffffff;
}

.search-container button {
    padding: 8px 16px;
    border: none;
    border-radius: 4px;
    background-color: #ffffff;
    color: #2c5d63;
    cursor: pointer;
}
//App.js

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

function App() {
    const [musicList, setMusicList] = useState([]);
    const [newMusic, setNewMusic] =
        useState({
            title: '', artist: '',
            genre: '', releaseDate: '',
            songUrl: ''
        });
    const [searchTerm, setSearchTerm] = useState('');
    const [currentSong, setCurrentSong] = useState(null);
    const [audioPlayer, setAudioPlayer] = useState(null);
    // Track playing state
    const [isPlaying, setIsPlaying] = useState(false);

    useEffect(() => {
        axios.get('http://localhost:3001/api/music')
            .then(response => setMusicList(response.data))
            .catch(error =>
                console.error('Error fetching music:', error));
    }, []);

    const handleSearch = () => {
        // Trim whitespace from search term
        let searchQuery = searchTerm.trim();

        if (searchQuery === '') {
            // If search query is empty, fetch all songs
            axios.get('http://localhost:3001/api/music')
                .then(response => 
                    setMusicList(response.data))
                .catch(error => 
                    console.error('Error fetching music:', error));
        } else {
            // Otherwise, perform search with non-empty query
            axios.get(
`http://localhost:3001/api/music?search=${searchQuery}`)
                .then(response => setMusicList(response.data))
                .catch(error => 
                    console.error('Error searching music:', error));
        }
    };

    const handleInputChange = e => {
        const { name, value } = e.target;
        setNewMusic(
            prevState => ({ ...prevState, [name]: value }));
    };

    const handleSubmit = e => {
        e.preventDefault();
        if (!newMusic.title ||
            !newMusic.artist ||
            !newMusic.genre ||
            !newMusic.releaseDate ||
            !newMusic.songUrl) {
            console.error('Please fill in all fields.');
            return;
        }

        axios.post('http://localhost:3001/api/music', newMusic)
            .then(response => {
                setMusicList(
                    prevState => [...prevState, response.data]);
                setNewMusic({
                    title: '', artist: '',
                    genre: '', releaseDate: '',
                    songUrl: ''
                });
                console.log('Music added successfully.');
            })
            .catch(error => 
                console.error('Error adding music:', error));
    };

    const handleEdit = (id, currentMusic) => {
        let updatedTitle = 
            prompt("Enter updated title:", currentMusic.title);
        let updatedArtist = 
            prompt("Enter updated artist:", currentMusic.artist);
        let updatedGenre = 
            prompt("Enter updated genre:", currentMusic.genre);
        let updatedReleaseDate =
            prompt("Enter updated release date (YYYY-MM-DD):", 
                    currentMusic.releaseDate);
        let updatedSongUrl = 
            prompt("Enter updated song URL:", 
                    currentMusic.songUrl);

        // Check if any field is null or undefined
        if (updatedTitle === null || updatedTitle === undefined ||
            updatedArtist === null || updatedArtist === undefined ||
            updatedGenre === null || updatedGenre === undefined ||
            updatedReleaseDate === null ||
            updatedReleaseDate === undefined ||
            updatedSongUrl === null || updatedSongUrl === undefined) {
            console.error('Please fill in all fields.');
            return;
        }

        // Construct updatedMusic object
        const updatedMusic = {
            title: updatedTitle,
            artist: updatedArtist,
            genre: updatedGenre,
            releaseDate: updatedReleaseDate,
            songUrl: updatedSongUrl
        };

        axios.put(
`http://localhost:3001/api/music/${id}`, updatedMusic)
            .then(response => {
                const updatedList =
                    musicList.map(music =>
                        (music._id === id ? response.data : music));
                setMusicList(updatedList);
                console.log('Music edited successfully.');
            })
            .catch(error =>
                console.error('Error editing music:', error));
    };


    const handleDelete = id => {
        axios.delete(`http://localhost:3001/api/music/${id}`)
            .then(() => {
                const updatedList =
                    musicList.filter(
                        music => music._id !== id);
                setMusicList(updatedList);
                console.log('Music deleted successfully.');
            })
            .catch(error =>
                console.error('Error deleting music:', error));
    };

    const playSong = (songUrl) => {
        if (audioPlayer) {
            // Check if audio is paused before attempting to play
            if (audioPlayer.paused) {
                audioPlayer.src = songUrl;
                audioPlayer.load();
                audioPlayer.play();
                setIsPlaying(true); // Update playing state
                setCurrentSong(songUrl); // Set current song
            }
        }
    };

    useEffect(() => {
        if (audioPlayer) {
            audioPlayer.onended = () => {
                setCurrentSong(null);
                setIsPlaying(false); // Update playing state
            };
        }
    }, [audioPlayer]);

    return (
        <div className="App">
            <h1>Music Discovery App</h1>
            <form onSubmit={handleSubmit}>
                <input type="text" name="title" 
                       placeholder="Title" value={newMusic.title} 
                       onChange={handleInputChange} />
                <input type="text" name="artist" 
                       placeholder="Artist" value={newMusic.artist} 
                       onChange={handleInputChange} />
                <input type="text" name="genre" 
                             placeholder="Genre" value={newMusic.genre} 
                             onChange={handleInputChange} />
                <input type="date" name="releaseDate" 
                       value={newMusic.releaseDate} 
                       onChange={handleInputChange} />
                <input type="text" name="songUrl" 
                       placeholder="Song URL" value={newMusic.songUrl} 
                       onChange={handleInputChange} />
                <button type="submit">Add Music</button>
            </form>
            <div className="search-container">
                <input type="text" placeholder="Search music..." 
                       value={searchTerm} onChange={
                       (e) => setSearchTerm(e.target.value)} />
                <button onClick={handleSearch}>Search</button>
            </div>
            <div className="music-list">
                {musicList.map(music => (
                    <div className={
`music-item ${currentSong === music.songUrl ? 'playing' : ''}`} 
                         key={music._id}>
                        <div className="music-info">
                            <h3>{music.title}</h3>
                            <p>Artist: {music.artist}</p>
                            <p>Genre: {music.genre}</p>
                            <p>
Release Date: {new Date(music.releaseDate).toLocaleDateString()}
                            </p>
                            {currentSong === music.songUrl ? (
                                <button onClick={() => {
                                    audioPlayer.pause();
                                    setIsPlaying(false);
                                    setCurrentSong(null);
                                }}>Pause</button>
                            ) : (
                                <button onClick={() => {
                                    playSong(music.songUrl);
                                    setCurrentSong(music.songUrl);
                                }}>
{isPlaying && currentSong === music.songUrl ? 'Pause' : 'Play'}
                                </button>
                            )}
                        </div>
                        <div className="music-actions">
                            <button onClick={
                                () => handleEdit(music._id,
                                    { title: 'Updated Title' })}>
                                Edit
                            </button>
                            <button onClick={
                                () => handleDelete(music._id)}>
                                Delete
                            </button>
                        </div>
                    </div>
                ))}
            </div>
            <audio ref={(ref) => setAudioPlayer(ref)} />
        </div>
    );
}

export default App;


Step to Run Application: Run the application using the following command from the root directory of the project

npm start

Output: Your project will be shown in the URL http://localhost:3000/

3231-ezgifcom-video-to-gif-converter

Article Tags :