Calling a third party API in loopback after saving data - node.js

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.

Related

Meteor: Proper Way to Send a POST Request to Modulus API

I have a meteor application hosted on modulus.
Modulus has an api here: https://api.modulus.io/
The API code is hosted here: https://github.com/onmodulus/modulus-cli/blob/master/lib/librarian/librarian.js#L361
I want to save a domain to one of my deployments on modulus through the API.
I know this is the code I am looking to connect to: https://github.com/onmodulus/modulus-cli/blob/master/lib/librarian/librarian.js#L361
librarian.project.saveDomains = function(projectId, domains, authToken, callback) {
if(checkInit(callback)) {
librarian._http.request(util.format('/project/%s/domains?authToken=%s', projectId, authToken), 'POST', domains, callback);
}
};
And I am new to making API calls.
I believe I need to make the call from the server in my meteor app, which could be handled using a meteor method, but I don't know what the API call should look like.
I've researched online and on SO. No luck and I am battling a lack of experience with API calls so I am looking from a little direction.
I've added the http package for meteor:
meteor add http
I think the following is in the ball park, but not currently working:
POST
javascript
//save arguments object to options variable
var options = {
projectId: xxx,
domains: ["example.com"],
authToken: settings.Modulus.authToken
}
//call meteor method from client
Meteor.call('saveDomainToModulus', options, function(error, result) {
if (error)
console.log(error)
else
console.log(result)
}
});
//on server
Meteor.methods({
"saveDomainToModulus": function(options) {
var apiEndpoint = "http://api.modulus.io/project/%s/domains?authToken=" + options.authToken;
HTTP.post( apiEndpoint,
options.projectId,
options.domains,
options.authToken,
function( error, response ) {
if ( error ) {
console.log( error );
} else {
console.log( response );
}
});
}
})
Feels like I'm starting to close in on a solution, but if anyone with more experience has any feedback, let me know.

How to create loopback external CRUD?

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)

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.

Testing an API wrapper

I'm writing an API wrapper for an external API, to be used in our application.
I have adopted a test-driven approach for this project but since I have little to no experience with writing API wrappers, I'm not sure if I'm on the right track.
I understand that I should not be testing the external API, nor should I be hitting the network in my tests. I'm using Nock to mock my requests to the API.
However, I'm not sure I'm doing this correctly.
I made some requests to the API using curl and put the (XML) response in a file, for example: /test/fixtures/authentication/error.js:
module.exports = "<error>Authorization credentials failed.</error>"
Since I don't want to hit the network, but want to make sure my wrapper parses the XML to JSON, I figured I needed sample data.
My test looks like this:
describe("with an invalid application key", function() {
var cl, api;
before(function(done) {
api = nock(baseApi)
.get('/v1/auth/authenticate')
.reply(200, fixtures.authentication.error);
done();
});
after(function(done) {
nock.cleanAll();
done();
});
it("returns an error", function(done) {
cl = new APIClient(auth.auth_user, auth.auth_pass, "abcd1234");
cl.authenticate(function(err, res) {
should.exist(err);
err.should.match(/Authorization credentials failed./);
should.not.exist(res);
api.isDone().should.be.true;
done();
});
});
});
With my tested code looking like this:
APIClient.prototype.authenticate = function(callback) {
var self = this;
request({
uri: this.httpUri + '/auth/authenticate',
method: 'GET',
headers: {
auth_user: this.user,
auth_pass: this.pass,
auth_appkey: this.appkey
}
}, function(err, res, body) {
if (err) {
return callback('Could not connect to the API endpoint.');
}
self.parser.parseXML(body, function(err, result) {
if (err) { return callback(err); }
if (result.error) { return callback(result.error); }
self.token = result.auth.token[0];
return callback(null, result);
});
});
};
Now, this seems to be working fine for the authentication side of things (I also have a 'success' fixture, which returns the 'success' XML and I check if the returned JSON is actually correct.
However, the API I'm using also has endpoints like:
/data/topicdata/realtime/:reportxhours/:topics/:mediatypes/:pageIndex/:pageSize
I'm not sure how to test all (should I?) possible combinations with URLs like those. I feel like I can hardly put 30 XML responses in my fixtures directory. Also, when mocking responses, I'm afraid to miss out on possible errors, edge cases, etc. the external API might return. Are these valid concerns?
If anyone has any pointers, and/or knows of any open-source and well-tested API wrappers I could take a look at, I'd be very grateful.
I think your concern is very valid and I suggest you to also build tests using Zombie or other simular request-based testing frameworks.

Resources