Reputation: 4605
I am trying to learn MongoDB with a simple Express API. The API works well for fetching individual collections, but when I try to combine multiple collections into one schema using mongoose ref
, I get a json response containing only the _id
field.
Here are two Schemas in Mongoose:
var LinkSchema = require('./link.js');
var MyInfoSchema = new Schema({
name: String,
title: String,
links: [ LinkSchema ]
});
var AboutSchema = new Schema({
img: String,
description: String
});
These work fine in endpoints calling them separately.
I want to have an endpoint that combines these two collections into a single json response. So, I made this model using ref
var MyInfoSchema = require('./myinfo.js');
var AboutSchema = require('./about.js');
var AllDataSchema = new Schema({
myinfo: {
type: mongoose.Schema.ObjectId,
ref: 'MyInfoSchema'
},
about: {
type: mongoose.Schema.ObjectId,
ref: 'AboutSchema'
}
});
My Express API route looks like this
var router = express.Router();
router.route('/')
.get(function(request, response){
var data = new AllData();
// add myinfo
MyInfo.findOne(function(error, info){
data.myinfo = info;
console.log(data); // this gives an id
});
// add about
About.findOne(function(error, about){
data.about = about;
});
console.log(data); // this prints nothing
response.json(data);
});
However, the json response I get is only an _id
{ "_id": "59bfed783f0ba490a34c9ce9" }
Why is the response not populated?
I tried logging to the console. When I put the log inside the findOne()
, I get an object like this
{ _id: 59bfed783f0ba490a34c9ce9,
myinfo: 59b1ad02d551330000000002 }
When I place the log statement outside the find function and before response.json()
, I get nothing. Why is that? data
should still be in scope, no?
Edit:
As suggested by @Mikey in the answers, I've refactored to use Promises. This is getting closer, but there are 2 problems. 1. The keys for each collection are missing, and 2. It's returning an array for the root structure instead of an object.
Here is the update
.get(function(request, response){
var data = new AllData();
var p1 = MyInfo.findOne().exec(),
p2 = Navigation.findOne().exec(),
p3 = About.findOne().exec(),
p4 = Contact.findOne().exec();
Promise.all([p1, p2, p3, p4]).then(function(data) {
data.myinfo = data[0];
data.navigation = data[1];
data.about = data[2];
data.contact = data[3];
response.json(data);
});
});
and this is the response
[
{
"_id": "59b1ad02d551330000000002",
"title": "Television Tester",
"name": "Walter P. K.",
"__v": 0,
"links": [
{
"name": "...",
"url": "...",
"_id": "59b1ad02d551330000000004"
},
{
"name": "more...",
"url": "more...",
"_id": "59b1ad02d551330000000003"
}
]
},
{
"_id": "59b2e606103ace0000000003",
"__v": 1,
"links": [
{
"name": "About",
"url": "#",
"_id": "59b2e62d103ace0000000009"
},
{
"name": "Stuff",
"url": "#",
"_id": "59b2e62d103ace0000000008"
},
{
"name": "Contact",
"url": "#",
"_id": "59b2e62d103ace0000000007"
}
]
}
...
]
It should be something like this
{
myinfo: {}.
navigation: {},
about: {},
contact: {}
}
Upvotes: 0
Views: 909
Reputation: 4605
I was able to solve the problem with the helpful suggestions of the other answerers. Thank you for pointing out the async nature of find()
and promises. What I needed to make it work was to use a plain JS object for the response structure (instead of a Mongoose Schema), and some variable name cleanup. In the answer by Mikey, and my edit to the question, the response and console.log array being returned was actually the promises array (i.e. the values from p1, p2, p3, etc), not the data structure I wanted to return.
Here is the working code:
router.route('/')
.get(function(request, response){
var p1 = MyInfo.findOne().exec(),
p2 = Navigation.findOne().exec(),
p3 = About.findOne().exec(),
p4 = Contact.findOne().exec();
Promise.all([p1, p2, p3, p4]).then(function(docs) {
var data = {};
console.log(docs);
data.myinfo = docs[0];
data.navigation = docs[1];
data.about = docs[2];
data.contact = docs[3];
response.json(data);
});
});
Upvotes: 1
Reputation: 12071
findOne is async so your console log runs before the findOne callbacks.
You might be looking for something more like this, which will query your 'AllData' collection and replace the stored id with the referenced documents for 'myinfo' and 'about':
AllData.find()
.populate('myinfo about')
.exec(function(error, docs) {
console.log(docs)
response.json(docs)
});
Upvotes: 1
Reputation: 6766
Mongoose operations are asynchronous. You need to wait for all the operations to be done before sending a response.
You can either nest your operations
router.route('/').get(function(request, response){
var data = new AllData();
MyInfo.findOne(function(err, info) {
data.myinfo = info;
About.findOne(function (err, about){
data.about = about;
response.json(data);
});
});
});
or use promises (I think it's like this)
router.route('/').get(function(request, response){
var data = new AllData();
var p1 = MyInfo.findOne().exec(),
p2 = About.findOne().exec();
Promise.all([p1, p2]).then(function(data) {
data.myinfo = data[0];
data.about = data[1];
response.json(data);
});
});
Upvotes: 1