Search code examples
jsonnode.jspromisemysql2

Node mysql2 sub-promise / subquery and append results to json object


So my convoluted title may explain why I'm struggling to find an answer.

I am writing a REST API in node that returns a JSON object. I have a database with two tables, 'Adverts' and 'Photos' with many photos per advert. I am using mysql2 with promises.

I want to return an object with and array of adverts (this works) and each advert contains an array of photos. I am doing this by getting all adverts and then appending the photo data to the object.

If I return some hard coded data in my getAdvertPhotos function eg

var hard_coded = { count: 2, pics: [{ id:1, url:'pic1.jpg'},{ id:2, url:'pic2.jpg'}]};

return hard_coded;

then everything works as I want it to so I am pretty certain my issue is that the response from the call to .getAll happens before the database hits to get the photos have finished. But I don't know how to wait (await?) the response until all queries have completed.

I do have a view in the DB that joins both tables so all the results come through in one query rather than two but then I get duplicate row of advert details depending how many photos it has, which I fully understand at a DB level so there might just be a better way to process this result into an object instead?

exports.getAll = (req, res, next) => {
    db.selectAll('adverts', 'id', 'desc')
    .then(
        adverts => {
            adverts.map(advert => {
                advert.photos = getAdvertPhotos(advert.id);
            });

            res.status(200).json({
                message: 'GET to /adverts',
                count: adverts.length,
                adverts: adverts
            });
        }
    )
    .catch(
        error => {
            res.status(500).json({
                message: 'GET to /adverts',
                error: error
            });
        }
    );
};

function getAdvertPhotos(id){
    db.selectAllById('photos', 'advert_id', id, 'id', 'desc')
    .then(
        photos => {
            var json = {
                count: photos.length,
                pics: photos.map(photo => {
                    return {
                        id: photo.id,
                        request: {
                            type: 'GET',
                            url: photo.url
                        }
                    };
                })
            };

            return json;
        }
    )
    .catch(
        error => {
            console.log(error);
            }
    );
}

This is an example of the result I am hoping to see

{
    "message": "GET to /adverts",
    "count": 3,
    "adverts": [
        {
            "id": 1,
            "title": "Title 1",
            "price": 123,
            "description": "A description 1",
            "photos": {
                "count": 2,
                "pics": [
                    {
                        "id": 1,
                        "url": "pic1.jpg"
                    },
                    {
                        "id": 2,
                        "url": "pic2.jpg"
                    }
                ]
            }
        },
        {
            "id": 2,
            "title": "Title 2",
            "price": 456,
            "description": "A description 2",
            "photos": {
                "count": 2,
                "pics": [
                    {
                        "id": 1,
                        "url": "pic3.jpg"
                    },
                    {
                        "id": 2,
                        "url": "pic4.jpg"
                    }
                ]
            }
        },
        {
            "id": 3,
            "title": "Title 3",
            "price": 789,
            "description": "A description 3",
            "photos": {
                "count": 2,
                "pics": [
                    {
                        "id": 1,
                        "url": "pic5.jpg"
                    },
                    {
                        "id": 2,
                        "url": "pic6.jpg"
                    }
                ]
            }
        }
    ]
}

Solution

  • to "await" a promise without using the "await" keyword you just need to return it. Then your code would look like this:

    'use strict';
    
    function getAdvertPhotos(id){
        return db.selectAllById('photos', 'advert_id', id, 'id', 'desc')
        .then(
            photos => {
                var json = {
                    count: photos.length,
                    pics: photos.map(photo => {
                        return {
                            id: photo.id,
                            request: {
                                type: 'GET',
                                url: photo.url
                            }
                        };
                    })
                };
    
                return json;
            }
        )
        .catch(
            error => {
                console.log(error);
                }
        );
    }
    
    exports.getAll = (req, res, next) => {
        return db.selectAll('adverts', 'id', 'desc')
        .then(
            adverts => {
    
                const photoJobs = adverts.map(advert => {
    
                    return getAdvertPhotos(advert.id).then(photos => ({
                        advertId: advert.id,
                        photos
                    }));
                });
    
    
                return Promise.all(photoJobs).then(results => {
    
                    return adverts.map(advert => {
                        const res = results.find(r => r.advertId === advert.id);
                        advert.photos = res ? res.photos : [];
                        return advert;
                    });
                })
                .then(adverts => {
    
                    return res.status(200).json({
                        message: 'GET to /adverts',
                        count: adverts.length,
                        adverts: adverts
                    });
                });
            }
        )
        .catch(
            error => {
                res.status(500).json({
                    message: 'GET to /adverts',
                    error: error
                });
            }
        );
    };
    

    Not tested.