Combining multiple models for one controller - node.js

I have these 2 controllers:
story.js
const Story = require('../models/Story');
exports.getStories = (req, res, next) => {
Story.find()
.then((stories) => {
res.render('story', {title:'StoryTata', stories});
})
.catch(() => { res.send('Sorry!'); });
};
ask.js
const Ask = require('../models/Ask');
exports.getAsks = (req, res, next) => {
Ask.find()
.then((asks) => {
res.render('ask', {title:'AskTata', asks});
})
.catch(() => { res.send('Sorry!'); });
};
And both these controllers work fine with their coupled views (stories.pug and ask.pug)
I am unable figure out my next controller where both of these controllers come together to create a dashboard type view.
dashboard controller --
const Story = require('../models/Story');
const Ask = require('../models/Ask');
/**
* GET /
* Home page.
*/
exports.getHome = (req, res) => {
Story.find()
Ask.find()
.then((stories, asks) => {
res.render('home', {stories, asks});
})
.catch(() => { res.send('Sorry!'); });
};
I will requesting getHome from my home.pug file.
What should be the correct way of doing this?

Try using the async-await approach, like this:
exports.getHome = async (req, res) => {
try {
const stories = await Story.find();
const asks = await Ask.find();
res.render('home', {stories, asks});
} catch(e) {
res.send('Sorry!');
}
};

Related

express .get method callback function problem

code for tour.route.js :
app.get(tour_controller.getTourById)
tour_controller.js
getTourById = handler.getOne(TourModel);
handler.js
exports.getOne = (Model, popOptions) => {
catchAsync(async (req, res, next) => {
let query = Model.findById(req.params.id);
if (popOptions) {`your text`
query = query.populate(popOptions);
}
const docs = await query;
if (!docs) {
return next(
new apiError('No documents found with ID: ' + req.params.id, 404)
);
}
res.status(200).json({
status: 'succeed!',
data: docs,
});
});
};
problem that displays during npm start
I stuck here,
the same thing works for .post, .patch and .delete method but it shows error for .get method
You forgot to return
exports.getOne = (Model, popOptions) => {
return catchAsync(async (req, res, next) => {
// ...
});
};

Implement function in async

I want to implement a function in async. This code is for authentication, that if user is logged in, than can't reach the page without login. My code in app.js file worked, there is the isLoggedOut function, where i can implement, so this code is working, but I want to copy this code to my controller.js.
The working code in app.js:
app.get('/explore-random', isLoggedIn, (req, res) => {
const count = Recipe.find().countDocuments();
const random = Math.floor(Math.random() * count);
const recipe = Recipe.findOne().skip(random).exec();
res.render('explore-random', { title: 'Cooking Blog - Explore Random', recipe } );
})
The controller.js, where i want to implement isLoggedOut function to exploreRandom
function isLoggedIn(req, res, next) {
if (req.isAuthenticated()) return next();
res.redirect('/login');
}
function isLoggedOut(req, res, next) {
if (!req.isAuthenticated()) return next();
res.redirect('/home');
}
/**
* GET /explore-random
* Explore Random
*/
exports.exploreRandom = async (req, res) => {
try {
let count = await Recipe.find().countDocuments();
let random = Math.floor(Math.random() * count);
let recipe = await Recipe.findOne().skip(random).exec();
res.render('explore-random', { title: 'Cooking Blog - Explore Random', recipe } );
} catch (error) {
res.status(500).send({message: error.message || "Error Occured"});
}
}
You can simply pass the exported exploreRandom function as a handler to your app.get route:
const exploreRandom = require('./path/to/explore-random-module');
app.get('/explore-random', isLoggedIn, exploreRandom);

With epilogue / finale-rest, how can I accept binary files via multer?

I'm doing:
const audioResource = finale.resource({
model: db.models.Audio,
endpoints: ['/audios', '/audios/:id']
})
audioResource.use(multer().single("file"))
audioResource.use(resources.audio)
Where resources.audio is:
module.exports = {
create: {
write: {
before: (req, res, context) => {
console.log(Object.keys(req))
console.log(req.body)
console.log("HERE I AM!")
console.log(req.file)
}
}
}
}
However, I can't access req.file. Is it possible to set up with the multer middleware?
I think the right way to use it would be
module.exports = {
create: {
write: {
before: (req, res, context) => {
upload.single('file')(req, res, () => {
console.log(Object.keys(req))
console.log(req.body)
console.log("HERE I AM!")
console.log(req.file)
}
}
}
}
}

Calling an API endpoint from within another route in Node / Express

I have myRoute.js with a route (GET) defined and I want to call an api endpoint from another route (api.js), and I'm not sure what the right way to do this is. The api.js route is working properly (image and code below).
api.js
router.get('/getGroups/:uid', function(req, res, next) {
let uid = req.params.uid;
db.getAllGroups(uid).then((data) => {
let response =[];
for (i in data) {
response.push(data[i].groupname);
}
res.status(200).send(response);
})
.catch(function (err) {
return err;
});
});
works as expected:
myRoute.js
I would like when a user goes to localhost:3000/USER_ID that the route definition gets information from the api. Psuedo code below (someFunction).
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
let fromApi = someFunction(`localhost:3000/getAllGroups/${uid}`); // <--!!!
console.log(fromApi) ; //expecting array
res.render('./personal/index.jade', {fromApi JSON stringified});
});
Not sure if i understand you correct but anyway i will try to help. So you have an api like
router.get('/getGroups/:uid', function(req, res, next) {
let uid = req.params.uid;
db.getAllGroups(uid).then((data) => {
let response =[];
for (i in data) {
response.push(data[i].groupname);
}
res.status(200).send(response);
})
.catch(function (err) {
return err;
});
});
If you would like to reuse it you can extract a function from the code above like so:
async function getAllGroupsByUserId(uid){
const result = [];
try{
const data = await db.getAllGroups(uid);
for (i in data) {
result.push(data[i].groupname);
};
return result;
}
catch(e) {
return e;
}
}
And then reuse it in your api & anywhere you want:
router.get('/getGroups/:uid', async function(req, res, next) {
const uid = req.params.uid;
const groups = await getAllGroupsByUserId(uid);
res.status(200).send(groups);
})
Same you can do in your another route:
router.get('/:uid', async function(req, res, next) {
const uid = req.params.uid;
const fromApi = await getAllGroupsByUserId(uid); // <--!!!
console.log(fromApi) ; //expecting array
res.render('./personal/index.jade', {fromApi JSON stringified});
});
Seems like pretty clear :)
I would use fetch for this. You can replace someFunction with fetch, and then put the res.render code in a .then(). So, you would get this:
const fetch = require("node-fetch");
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
fetch('localhost:3000/getAllGroups/${uid}').then(res => res.json()).then(function(data) {
returned = data.json();
console.log(returned); //expecting array
res.render('./personal/index.jade', {JSON.stringify(returned)});
});
});
A more robust way with error handling would be to write something like this:
const fetch = require("node-fetch");
function handleErrors(response) {
if(!response.ok) {
throw new Error("Request failed " + response.statusText);
}
return response;
}
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
fetch('localhost:3000/getAllGroups/${uid}')
.then(handleErrors)
.then(res => res.json())
.then(function(data) {
console.log(data) ; //expecting array
res.render('./personal/index.jade', {JSON.stringify(data)});
})
.catch(function(err) {
// handle the error here
})
});
The ideal way would be to abstract your code into a method so you aren't calling yourself, as The Reason said. However, if you really want to call yourself, this will work.

Display all collections in mongodb+express

This is my code for sending data to a database:
app.post('/thanks', function(req, res) {
if (atendees.checkin === req.body.dbstring) {
dbConn.then(client => {
delete req.body._id;
const db = client.db('mydata')
db.collection(atendees.checkin).insertOne(req.body);
})
(...)
This is how I display on the page after clicking on a href link:
app.get('/view-feedbacks', function(req, res) {
dbConn.then(client => {
const db = client.db('mydata')
db.collection(atendees.checkin).find({}).toArray().then(function(feedbacks) {
res.status(200).json(feedbacks);
atendees.checkin = ' '
}).catch(err => {
throw(err);
})
});
});
That works fine. How can I do something similar to display all collections from the database instead of just the individual ones?
This is what I tried to do:
app.get('/view-history', function(req, res) {
dbConn.then(client => {
const db = client.db('mydata')
db.listCollections().toArray().then(function(collInfos) {
res.status(200).json(collInfos);
atendees.checkin = ' '
}).catch(err => {
throw(err);
})
});
});
But it just gives me the name of each collection. I want to show all collections and all of their elements.
Edit: my question is different from this one: MongoDB Show all contents from all collections .I'm trying to do this on express.js, not on the terminal
Edit2: Using db.collection:
app.get('/view-history', function(req, res) {
dbConn.then(client => {
const db = client.db('mydata')
db.collections().then(function(feedbacks) {
res.status(200).json(feedbacks);
atendees.checkin = ' '
}).catch(err => {
throw(err);
})
});
But this gives the error: TypeError: converting circular structure to JSON
With async/await, this could be done:
app.get('/view-history', async (req, res) => {
try {
const client = await dbConn;
const db = client.db('mydata');
let collections = await db.collections();
let documents = await Promise.all(collections.map(async (collection) => {
let documents = await collection.find({}).toArray();
return Promise.resolve([collection.collectionName, documents]); // Retain collectionName
}));
// Format into an object that looks like `collectionName: documents`
let formatted = documents.reduce((obj, collection) => {
obj[collection[0]] = collection[1];
return obj;
}, {});
res.json(formatted);
} catch (e) {
console.error(e);
res.sendStatus(500);
}
});
A Promise-only approach:
app.get('/view-history', (req, res) => {
dbConn.then((client) => {
const db = client.db('mydata');
return db.collections();
}).then((collections) => {
return Promise.all(collections.map((collection) => {
return new Promise((resolve, reject) => {
collection.find({}).toArray().then((documents) => {
resolve([collection.collectionName, documents]);
}).catch(reject);
});
}));
}).then((documents) => {
let formatted = documents.reduce((obj, collection) => {
obj[collection[0]] = collection[1];
return obj;
}, {});
res.json(formatted);
}).catch((e) => {
console.error(e);
res.sendStatus(500);
});
});
The main reason this code is unnecessarily verbose is because instead of just returning a big array filled with arrays of documents, you probably want an object that retains the name of the collection, like so:
{
collection1: [...documents...],
collection2: [...documents...],
...
}
Instead of:
[
[...documents...],
[...documents...],
...
]
If you do want just a big array of each collection without caring about the names of the collections, it becomes much simpler:
async/await version:
app.get('/view-history', async (req, res) => {
try {
const client = await dbConn;
const db = client.db('mydata');
let collections = await db.collections();
let documents = await Promise.all(collections.map((collection) => collection.find({}).toArray()));
res.json(documents);
} catch (e) {
console.error(e);
res.sendStatus(500);
}
});
Promise-only version:
app.get('/view-history', (req, res) => {
dbConn.then((client) => {
const db = client.db('mydata');
return db.collections();
}).then((collections) => {
return Promise.all(collections.map((collection) => collection.find({}).toArray()));
}).then((documents) => {
res.json(documents);
}).catch((e) => {
console.error(e);
res.sendStatus(500);
});
});
Have you tried just db.collections()? If that also doesn't give what you need, you might have to invoke db.collection(<name>) on each of the names you get from listCollections.

Resources