Define/Use a promise in Express POST route on node.js - node.js

I currently have a POST route defined in an Express Node.js application as so:
var locationService = require("../app/modules/locationservice.js");
app.post('/createstop', isLoggedIn, function(req, res) {
locationService.createStop(res, req.body);
});
(for this question, please assume the routing in & db works.. my record is created on form submission, it's the response I am struggling with)
In the locationservice.js class I then currently have
var models = require('../models');
exports.createStop = function(res, formData) {
models.location.build({ name: formData.name })
.save()
.then(function(locationObj) {
res.json({ dbResult : locationObj });
});
};
So as you can see, my route invokes the exported function CreateStop which uses the Sequelize persistent layer to insert a record asynchronously, after which I can stick the result on the response in the promised then()
So at the moment this only works by passing the response object into the locationservice.js method and then setting res.json in the then() there. This is sub-optimal to me with regards to my service classes, and doesn't feel right either.
What I would like to be able to do is "treat" my createStop method as a promise/with a callback so I can just return the new location object (or an error) and deal with it in the calling method - as future uses of this method might have a response context/parameter to pass in/be populated.
Therefore in the route I would do something more like:
var locationService = require("../app/modules/locationservice.js");
app.post('/createstop', isLoggedIn, function(req, res) {
locationService.createStop(req.body)
.then(dataBack) {
res.json(dataBack);
};
});
Which means, I could call createStop from else where in the future and react to the response in that promise handler. But this is currently beyond me. I have done my due diligence research, but some individual expert input on my specific case would be most appreciated.

Your locationservice.js could look like that
exports.createShop = function(data){
// here I have used create instead of build -> save
return models.location.create(data).then(function(location){
// here you return instance of saved location
return location;
});
}
And then your post() method should be like below
app.post('/createstop', isLoggedIn, function(req, res){
locationService.createShop(req.body).then(function(location){
// here you access the location created and saved in createShop function
res.json(location);
}).catch(function(error){
// handle the error
});
});

Wrap your createStop function with a promise like so:
exports.createStop = function(res, formData) {
return new Promise(function(resolve, reject) {
models.location.build({ name: formData.name })
.save()
.then(function(locationObj) {
resolve({ dbResult : locationObj });
});
//in case of error, call reject();
});
};
This will allow you to use the .then after the createStop within your router.

Related

how to read specific data from realtime database in node.js

I am working on API Get request. I have created a POST request to add the data in firebase realtime database. The code is as follows:
// CREATE POST
app.post("/post", (req, res) => {
let key;
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in.
var newPost = firebase.database().ref("posts/");
var myPost = newPost.push({
createdBy: user.uid,
from: req.body.from,
to: req.body.to,
duration: req.body.duration,
comments: req.body.comments,
});
res.send(newPost);
const postId = myPost.key;
console.log(postId);
} else {
// No user is signed in.
res.status(404).send("No user is signed in right now!");
}
});
});
Now, in order to get a specific post, I have written the following code:
// GET SPECIFIC POST
app.get("/post/:id", (req, res) => {
let response;
firebase
.database()
.ref("posts/" + req.params.id)
.on("value", (snapshot) => {
response = snapshot.val();
});
res.send(response);
});
I am new at Firebase, so I dont really know how to get a specific post. Please help me out
Calls to Firebase are asynchronous, because they require a call to the server. While that call is happening, your main code continues. And then when the data is available, your callback is invoked with the data from the server.
Right now your res.send(response) runs before the response = snapshot.val() is ever called. The rule with asynchronous APIs is simple: any code that needs the data needs to be inside the callback, or be called from there.
So in your case:
app.get("/post/:id", (req, res) => {
firebase
.database()
.ref("posts/" + req.params.id)
.once("value")
.then((snapshot) => {
res.send(snapshot.val());
});
});
You'll note that I also change from on to once, since you only care about getting the value once (instead of attaching a permanent listener that monitors the database for changes).
Dealing with asynchronous API is a common stumbling block, so I recommend spending some time reading these answers to learn more:
Why Does Firebase Lose Reference outside the once() Function?
Firebase response is too slow
Best way to retrieve Firebase data and return it, or an alternative way
I simply did this:
app.get("/post/:id", (req, res) => {
var key = req.params.id;
console.log(key);
firebase
.database()
.ref("posts")
.child(key)
.get()
.then((snapshot) => {
res.send(snapshot.val());
});
});
this solved the problem

Error: Can't set headers after they are sent Braintree

I am currently working on an admin panel for this website I am creating, so I am able to accept payments via Braintree but I need to implement the ability to retrieve a customers transactions but once a header is sent it sends just one of them and not the whole thing. Is it possible to combine the json to an array so it will send in the one header?
CODE:
router.get('/:cid/test', function(req, res) {
var stream = gateway.transaction.search(function (search) {
search.customerId().is(req.params.cid);
}, function (err, response) {
response.each(function (err, transaction) {
return res.render('admin/test', {transaction: transaction});
});
});
});
This is solely following the Braintree documentation and I know exactly why the error occurs. Any help is really appreciated and I am terrible at explaining so if you need to know more information please give me a holler!
UPDATE: So, I figured I would explore another method and I noticed the 'response' gives back an array of ids. So I will just use EJS to loop through all those and then have a seperate page for each transaction.
Disclaimer: I work for Braintree :)
As Robert noted, you can only call res.render (or any of the response methods that end the request) once per request (hence the error from express).
Unfortunately, you cannot treat response as an array, so you will need to use
one of the two documented ways of interacting with search responses. I personally prefer the streams approach because it is clearer:
app.get('/stream', function (req, res) {
var transactions = []
var transactionStream = gateway.transaction.search(function (search) {
search.customerId().is(req.params.cid);
})
transactionStream.on('data', function (transaction) {
transactions.push(transaction)
})
transactionStream.on('error', function () { /* handle errors */ })
transactionStream.on('end', function () {
res.json({transactions: transactions});
})
})
Alternately, you can use the ids property of response to compare the transactions array that you build from each to know when to end the request:
app.get('/calback', function (req, res) {
var transactionStream = gateway.transaction.search(function (search) {
search.customerId().is(req.params.cid);
}, function (err, response) {
var transactions = []
response.each(function (err, transaction) {
transactions.push(transaction)
if (transactions.length === response.ids.length) {
res.json({transactions: transactions});
}
})
})
})
You can only render one response per route. So you can only call this once and not in a loop:
res.render('admin/test', {transaction: transaction}); });
You can use the each method to iterate through the response and build up a result:
var transactions =[];
response.each(function (err, transaction) { transactions.push(transaction) });
return res.render('admin/test', {transaction: transactions});
That would work if the each method is synchronous. If it's not (and Nick would know), use the solution below.

Testing a function which consumes promises

I've got a MEAN app and I'm trying to get tests to work on the node side. Async events are wrapped in promises, which are consumed in the controller. I failed at testing the controller :(
The controller I'm trying to test:
ProjectController.prototype.getAll = function(req, res, next) {
req.dic.subjectRepository
.getById(req.params.subjectId)
.then(function(subject) {
res.json(subject.projects);
}, function(err) {
return res.status(404).send('Subject does not exist.' + err);
});
};
The subjectRepository is our data source, which returns a promise (mpromise because under the hood we're using mongoose, but it shouldn't matter):
So in our test we tried mocking the request (we're injecting our dependency injection container from a middleware into the req) and response (the test succeeds if response.json() has been called with the subjects we tried to fetch) and our subjectRepository. We used bluebird (although I tried others out of frustration) to create fake promises for our mocked subjectRepository:
describe('SubjectController', function() {
'use strict';
var Promise = require('bluebird');
it('gets all existing subjects', function() {
// -------------------------------------
// subjectRepository Mock
var subjectRepository = {
getAll: function() {},
};
var subjectPromise = Promise.resolve([
{name: 'test'},
{name: 'test2'},
]);
spyOn(subjectRepository, 'getAll').andReturn(subjectPromise);
// -------------------------------------
// request mock
var req = {
dic: {
subjectRepository: subjectRepository,
},
};
// -------------------------------------
// response mock
var res = {
json: function() {},
send: function() {},
};
spyOn(res, 'json');
// -------------------------------------
// actual test
var subjectController = new (require('../../../private/controllers/SubjectController'))();
subjectController.getAll(req, res);
// this succeeds
expect(subjectRepository.getAll).toHaveBeenCalled();
// this fails
// expect(res.json).toHaveBeenCalled();
});
});
Question: How do I make the test run the expect() AFTER the promise succeeded?
Node v0.12
The code is on GitHub for anyone who's interested: https://github.com/mihaeu/fair-projects
And maybe I should mention that the controller is called from the router:
// router handles only routing
// and controller handles data between view and model (=MVC)
subjectRouter.get('/:subjectId', subjectController.get);
I got this to work by changing our controllers to hand down the promises, but I'm not sure this is what we want. Isn't there a way to get my approach to work?
it('gets all existing subjects', function(done) {
// ...
var subjectController = new (require('../../../private/controllers/SubjectController'))();
subjectController.getAll(req, res).then(function() {
expect(res.json).toHaveBeenCalledWith(testSubjects); // success
}).finally(done);
expect(subjectRepository.getAll).toHaveBeenCalled(); // success
}
Your code makes the mistake of mixing business logic with front facing routing.
If your getAll did not touch the request and response object, it would look something like this:
ProjectController.prototype.getAll = function(subjectId) {
return req.dic.subjectRepository.getById(subjectId).then(function(subject){
return subject.projects;
});
};
Now, it is no longer related to the request response life cycle or in charge of logic, testing it is trivial by:
it("does foo", function(){
// resolve to pass the test, reject otherwise, mocha or jasmine-as-promised
return controller.getAll(152).then(...)
});
That would make your actual handler look like:
app.get("/projects", function(req, res){
controller.getAll(req.params.subjectId).then(function(result){
res.json(result);
}, function(){
res.status(404).send("...");
});
});

rendering JSON in view

I am writing an app in node.js, I have the following code.
API for retrieving topic from DB
allTopics = function (req, res) {
db.Topic.all({limit: 10}).success(function (topics) {
res.send(topics)
});
};
Route for topics index
app.get('/topics', function (req, res){
res.render('topics/index.ejs',{ topics : allTopics })
});
Is the above code correct for route?
Also I have index.ejs file where I want to list all the topics (i.e. retrieve data from json response). How do I achieve this?
Your code as-is won't work but you could rewrite it as follows:
// notice how I am passing a callback rather than req/res
allTopics = function (callback) {
db.Topic.all({limit: 10}).success(function (topics) {
callback(topics);
});
};
// call allTopics and render inside the callback when allTopics()
// has finished. I renamed "allTopics" to "theData" in the callback
// just to make it clear one is the data one is the function.
app.get('/topics', function (req, res){
allTopics(function(theData) {
res.render('topics/index.ejs',{ topics : theData });
});
});

How do I return an error message to the client after failed Mongoose validation in Express

I have a very basic POST route that creates a new user out of the given data.
Here's how I save it:
app.post('/create', function(req, res){
var firstUser = new models.User(req.body);
firstUser.save();
});
My Mongoose User schema has a few validation options, which work. If I pass the wrong data, validation fails and the user isn't created.
But there's a problem: Mongoose's save() function is asynchronous, so how do I let the client now the validation failed?
The post function will be done by then.
You should have a look at Mongoose documentation. Because save method is asynchronous it takes a callback as parameter. This is where you should check for errors.
app.post('/create', function(req, res){
var firstUser = new models.User(req.body);
firstUser.save(function (err) {
if (err) {
res.send(500, { error: 'Saving first user failed!' });
} else {
res.send({ success: 'Saved!' });
}
})
});

Resources