Search code examples
node.jsmongodbmultipartform-dataaudio-streaming

How to handle audio and json data together in nodejs & mongodb


I Developing backend api with nodejs and mongodb. My Requirenment is to store Audion file along with its metadata like artist,album etc. i am using mongodb GridFs to store my audio file. In this my audio file is stored in chunks unser songs.chunks and metadata stored under songs.files.matadata

My requirenment is when user request for any song then my app shoud response audio file along with its matadata so that user can play audio as well as see matadata.

i have access to matadata and audio in controller which handle user request but i can't figure out how to send audio with metadata.

This is my code.

       const id = new ObjectId(req.params.id)

        const filesCollection = db.collection('songs.files');
        const data = await filesCollection.find(id).toArray();

        const matadata = data[0].matadata;

        

        res.set('content-type', 'audio/mpeg');
        res.set('accept-ranges', 'bytes');
        
        let bucket = new mongodb.GridFSBucket(db, {
            bucketName: 'songs'
        });
        

        let downloadStream = bucket.openDownloadStream(id);
        // console.log(downloadStream.name);

        
        downloadStream.on('data', (chunk) => {
            res.write(chunk);
        });

        downloadStream.on('error', (err) => {
            console.log(err);
            res.sendStatus(404);
        });
        
        downloadStream.on('end', () => {
            res.end();
        });


Solution

  • To send both audio data and metadata together in your response, you can send a custom response format, such as JSON.

    const id = new ObjectId(req.params.id);
    
    const filesCollection = db.collection('songs.files');
    const data = await filesCollection.find(id).toArray();
    
    const matadata = data[0].matadata;
    
    res.set('content-type', 'application/json'); // Set the response content type to JSON
    
    let bucket = new mongodb.GridFSBucket(db, {
        bucketName: 'songs'
    });
    
    let downloadStream = bucket.openDownloadStream(id);
    
    const chunks = [];
    downloadStream.on('data', (chunk) => {
        chunks.push(chunk);
    });
    
    downloadStream.on('error', (err) => {
        console.log(err);
        res.status(404).json({ error: 'File not found' });
    });
    
    downloadStream.on('end', () => {
        const audioData = Buffer.concat(chunks);
        const response = {
            metadata: matadata,
            audioData: audioData.toString('base64') // Convert audio data to base64
        };
        res.json(response); // Send the JSON response containing metadata and audio data
    });
    

    PS: The JSON response format includes both the metadata and the base64-encoded audio data. On the client side, you'll need to decode the base64 audio data.

    EDIT: Yes, it is possible to send an audio stream along with metadata in a single HTTP response.

    Here is the code for that:

    const id = new ObjectId(req.params.id);
    
    const filesCollection = db.collection('songs.files');
    const data = await filesCollection.find(id).toArray();
    
    const metadata = data[0].metadata;
    
    res.set('content-type', 'audio/mpeg');
    res.set('accept-ranges', 'bytes');
    res.set('X-Song-Metadata', JSON.stringify(metadata)); // Set metadata in custom header
    
    let bucket = new mongodb.GridFSBucket(db, {
        bucketName: 'songs'
    });
    
    let downloadStream = bucket.openDownloadStream(id);
    
    downloadStream.on('data', (chunk) => {
        res.write(chunk);
    });
    
    downloadStream.on('error', (err) => {
        console.log(err);
        res.sendStatus(404);
    });
    
    downloadStream.on('end', () => {
        res.end();
    });