How to create loopback external CRUD? - node.js

I want to use loopback to be a "middle man" to connect between two different api (server1 and server2). When I pass a user's email to this "middle man", it will check with server 1, and if that server returns true, then the "middle man" will get all data from the server 2 save to server 1.
Can someone guide me on how to do this? I tried to follow loopback documentation, but I couldn't get what I want, am I missing something?

So the actual implementation is not really related to Loopback.js framework. This can be implemented in any Node.js code.
And actually your description above is the the right steps to do it using a simple HTTP request to server 1 and 2
For example using request
module this code shows the pseudocode of the middle man.
const request = require('request');
// 1. Call Server 1
request('http://server1.com/api/message', function (err, res, body) {
// 2. Check body message is true
if (body.message === true) {
// 3. Get data from Server 2
request('http://server2.com/api/get', function (err, res, body) {
// 4. Save data to Server 1
request({
url: 'http://server1.com/api/save',
method: "POST",
json: requestData },
function (err, res body) { /* process response here... */ });
});
}
});

Assuming you have set up two datasources that call your external API providers as per my answer to your related question - for this example you would need the following calls:
Server1 datasource:
checkEmail // GET method, returns true or false
save // POST method, returns true or false
Server2 datasource:
getDetails // GET or POST method, returns contact details for the email address
You would also need a Loopback Model with an appropriate method as your entry point. For the example lets call it Contact, with a remote method called store, taking in the email parameter. This code would do all of the work:
Contact.store = function(email, cb) {
var server1 = Contact.app.dataSources.Server1;
var server2 = Contact.app.dataSources.Server2;
server1.checkEmail(email, function(validated) {
if(validated) {
server2.getDetails(email, function(details) {
if(details) {
server1.save(details, function(success) {
if(success) {
cb(null, details);
}
})
}
})
}
})
}
(you can promisify all of the above for cleaner code)

Related

xhttp GET request to an express.js server - nothing returned

I am trying to do a simple xhttp GET request to an express.js server. Unfortunately I get no response data with this code. The connection is fine as I have successfully used "res.send" to send a body back from the server.
I am not sure if my use of "findOne" on the server is incorrect or if my use of xhttp on the client is incorrect. I suspect it is the client.
I'd appreciate any advice.
* CLIENT CODE *
function getfood() {
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "http://localhost:3000/clientfood", true);
xhttp.send();
}
* SERVER CODE - Express.js / Node *
app.get('/clientfood', cors(), (req, res) => {
//res.send('test'); //this works at least
db.collection('quotes').findOne({
"_id": ObjectId("12345")
},
{
name: 1,
quote: 1
})
})
xhttp GET request to an express.js server - nothing returned
Your server code does not return a response. You need to do something like res.send(...) or res.json(...) to return a response back to the caller and you need to do that in the callback that your database provides for communicating back the result of a query (in most DBs, you can either use a plain callback or a promise).
Your client code does not listen for a response. Example for how to do that shown here on MDN and would typically be:
function getfood() {
var xhttp = new XMLHttpRequest();
xhttp.addEventListener("load", function() {
if (xhttp.status === 200) {
// have data in xhttp.responseText, process it here
} else {
// got some other response here
}
});
xhttp.open("GET", "http://localhost:3000/clientfood", true);
xhttp.send();
}
Thanks so much - especially #jfriend00. I have a lot to learn about how these frameworks work. After taking your advice about SEND I had a little trouble seeing the result on my frontend. I got the message "promise pending". I fixed that with the code suggested in this post.
Express - Promise pending when loop queries
Also I modified my findOne function to grab the entire object for my id.
Final code:
app.get('/clientfood', cors(), (req, res) => {
mydata = db.collection('quotes').findOne(
{
"_id": ObjectId("12345")
})
// promise code
Promise.all([mydata]).then(listOfResults => {
res.send(JSON.stringify(listOfResults)) //for example
}, err => {
res.send(500, JSON.stringify(err)); // for example
});
})

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.

Calling a third party API in loopback after saving data

I have just started with loopback.We are creating a middleware in loopback where a user comes and Fills up a task form. When he submits the form we save the data in our database and then we need to call a third party REST API to store the same data appended with ID which our DB returns.
I read about the REST connector in loopback, but I don't think it serves the purpose. So I decided to go ahead with the request library.
I have created a RemoteMethod where I am doing all this. Here's the code:-
const request = require('request');
const url = "http://third-party-api";
module.exports = function(Tasks) {
function cb(err, data){
return { data: data };
}
Tasks.add = function(data, cb){
Tasks.create({data: data}, function(err, object){
var taskId = object.id
request.get({url: url}, function(error, response, body){
cb(error, body);
});
});
};
Tasks.remoteMethod(
'add',
{
accepts: [{arg: 'data', type: 'object'}],
returns: {arg: 'result', type: 'object'},
http: {path: '/add', verb: "post"}
}
);
};
But this results in an error(IP changed):-
{"error":{"name":"Error","status":500,"message":"connect ECONNREFUSED 0.0.0.0","code":"ECONNREFUSED","errno":"ECONNREFUSED","syscall":"connect","address":"0.0.0.0"}}
I haven't been able to figure out what's going on here. NodeJs, Express, Loopback everything is pretty new to me & I have been going around in circles for quite sometime now.
What am I doing wrong? Is there any other approach that I need to follow
0.0.0.0 means all IP on your local machine, localhost.
Most likely, you forgot to define the port on which the external api is listening.
If your external API is listening on port 3001 for instance, then just modify your code like this
const url = "http://localhost:3001";
Just a note, there is nothing wrong with using request for your use case, but the loopback REST connector will also do exactly what you need.

How to pass value from web service to route and then to database call in Express and NodeJS

I am creating a small project to become familiar with NodeJS, Express and the MEAN stack in general. I'm also very new to web development.
I want to add search functionality to my little site. I have a controller, web service and a javascript file that contains all my database calls (MongoDB).
My question is: How do you pass the search value entered by the user from the web service to the route and then to the db? I've looked everywhere and but I have been unable to find a concrete example. This is what I've got so for.
My controller calls my web service.
this.search = function(searchValue,callback) {
console.log(searchValue);
$http({
method: 'GET',
url: '/contacts/search/:searchValue',
params: {searchValue: searchValue},
headers: {'Content-type': 'application/json'}
}).success(function(resp){
console.log("here");
callback(resp);
}).error(function(){
callback(undefined);
});
};
Next, my web service calls my route...
router.get('/search/:searchValue', function(req, res) {
db.search(req.params.searchValue, function(err,data){
if(!err) {
res.json(data);
}else{
res.json({code:-1,err:err});
}
});
});
Then the database call...
database.prototype.search = function(id,callback){
mongo.connect(dbUrl, function(err, db) {
if(!err) {
db.collection('friends',function(err,coll){
coll.find({friend:"Jimmy"}).toArray(function(err, items) {
db.close();
callback(null,items);
});
});
}else{
db.close();
console.log("hi");
callback(err,null);
}
});
};
Things work fine when I hard code my search value right into my db call (ie. "Jimmy" above). However, I don't know how to pass the search value from my web service to my route and then to the db. I get errors like the route cannot be found or I cannot connect to the database. Silly things that go away when I hard code values.
Anyhow, thank you for your time and patience.
In your router (what you call your web service) you're calling your database search function like this:
router.get('/search/:searchValue', function(req, res) {
db.search(req.params.searchValue, function(err,data){
...
Notice you're passing to your db.search req.params.searchValue
But in your database you have your same search function defined as:
database.prototype.search = function(id, callback){...
which as you can see, takes id as argument.
There's clearly a disconnect here. You're passing it the searchValue from router but you've defined it to take id.
Then further down in database search function you doing this:
database.prototype.search = function(id,callback){
mongo.connect(dbUrl, function(err, db) {
if(!err) {
db.collection('friends',function(err,coll){
coll.find({friend:"Jimmy"}).toArray(function(err, items) {
...
you're calling coll.find to which you should presumably want to pass that searchValue. There's another disconnect here, you're never using the id that you took as a parameter.
If you say that for things to work fine all you gotta do is put "Jimmy", which I guess is the searchValue, then you should try this:
database.prototype.search = function(searchValue,callback){ // replace id with searchValue
mongo.connect(dbUrl, function(err, db) {
if(!err) {
db.collection('friends',function(err,coll){
// use searchValue here
coll.find({friend:searchValue}).toArray(function(err, items) {
...
edit
There's some problem on your client side code as well
$http({
method: 'GET',
url: '/contacts/search/:searchValue',
params: {searchValue: searchValue},
headers: {'Content-type': 'application/json'}
Here you're making an AJAX call with Angular's $http module. And you're making a GET request but you're passing params along with it, which is usually only passed when you make a POST request. Also, the way you've defined your route you're only reading searchValue from the URL itself. So the URL here should be like this:
$http({
method: 'GET',
url: '/contacts/search/' + searchValue,
// params: { no need for params }
...
To explain a bit more how URL params work:
If you request a URL like this
GET /contacts/search/abcd
then you'd define your Express route handler like this
app.get('/contacts/search/:name', function(req, res, next){
req.params.name //=> "abcd"
});
Notice the syntax of route definition /contacts/search/:name is only used for defining the route in Express.
The /:name part is just to assign a variable name - name to the value - "abcd" so it could be accessed as req.params.name
Which is why this is wrong
$http({
method: 'GET',
url: '/contacts/search/:searchValue',
it should be this
$http({
method: 'GET',
url: '/contacts/search/' + yourActualSearchValue,

Node+ElasticSearch: Sending a body on a GET request?

I am using Node.js and the request module to create a backend, and we've chose Elasticsearch as our data storage. All fine so far, except it seems Node doesn't support request bodies on GET requests? This is necessary for Elasticsearch's _search API, which expects only GET requests as part of their semantic design. Is there a solution to force Node to send the request body even in the cases of a GET request, or a mean to use _search on Elasticsearch with another HTTP verb?
function elasticGet(url, data) {
data = data || {};
return Q.nfcall(request.get, {
uri: url,
body: JSON.stringify(data) //<-- noop
}).then(function(response) {
return JSON.parse(response[1]);
}, function(err) {
console.log(err);
});
}
The _search API also accepts the POST verb.
For simplicity, why not use their api rather than manually making requests?
simple example:
var elasticsearch = require('elasticsearch'),
client = new elasticsearch.Client({
host: '127.0.0.1:9200',
log: 'trace'
});
client.search({
index: '[your index]',
q: 'simple query',
fields: ['field']
}, function (err, results) {
if (err) next(err);
var ids = []
if (results && results.hits && results.hits.hits) {
ids = results.hits.hits.map(function (h) {
return h._id;
})
}
searchHandler(ids, next)
})
You can combine it with fullscale labs elastic.js to build really complex queries, really fast.
https://github.com/fullscale/elastic.js
I had such an issue a few days ago.
tld;dr use POST
According to https://www.elastic.co/guide/en/elasticsearch/guide/current/_empty_search.html#get_vs_post you can also use POST with elastic.
I tried it with axios but it returns all data like with no body.
So I used POST instead. It works for me and I hope it will help to someone else.

Resources