Call a hapi route from another route - javascript

I am pretty new to HapiJS. I am building a service where I have two routes /route1 and /route2 both are using the plugin architecture. I have registered both as plugins on my manifest file.
I want to call /route1 from /route2 so /route2 depends on the payload reply from /route1. I've been looking at putting the logic of /route2 on /route1 on the pre-handler but I want to keep them separately.
Don't know how to call a registered plugin from another the thing is that both plugins (routes) are making networks requests. Thanks for reading.
Thanks.

As you specify that you don't want to use a shared handler/route prerequisite (which would be my first choice), you could make an actual request using a http client (Wreck, request, http or the like).
Another, more efficient way that doesn't involve actually making a network request is to use hapi's built-in server.inject() method provided by Shot. This will inject a request into your server and get the response, which you can use. Here's an example:
var Hapi = require('hapi');
var server = new Hapi.Server();
server.connection({ port: 4000 });
var plugin1 = function (server, options, next) {
server.route({
method: 'GET',
path: '/route1',
handler: function (request, reply) {
reply('Hello');
}
});
next();
};
plugin1.attributes = { name: 'plugin1' };
var plugin2 = function (server, options, next) {
server.route({
method: 'GET',
path: '/route2',
handler: function (request, reply) {
server.inject('/route1', function (res) {
reply(res.payload + ' World!');
});
}
});
next();
};
plugin2.attributes = { name: 'plugin2' };
server.register([plugin1, plugin2], function (err) {
if (err) throw err;
server.start(function (err) {
if (err) throw err;
console.log('Started');
});
});
Note that the fact the routes are in plugins here is irrelevant. I've merely included it so it's close to your situation.
Shot and server.inject() are primarily used for testing but there are legitimate runtime uses like this too.
If you make a request to /route2, this will invoke /route1 handler and get the payload:
$ curl localhost:4000/route2
Hello World!

Related

Storing API response in session

On POST of a page I'm trying to GET information from an API, return the output and store the results so it can be used across all pages. The method in which the API is called isn't the best but it's just for proof of concept rather than production quality. So here is the code at the moment:
router.post('/page-one/', function (req, res) {
var options = {
'method': 'GET',
'url': 'https://api.information.service.com/company/<value>',
'headers': {
'Authorization': 'Basic <key>'
}
}
request(options, function (error, response) {
if (error) throw new Error(error)
console.log(response.body)
})
res.redirect('/page-two/')
})
So this works fine, the console returns the right information. How would I then take this and use it across all pages? Say on page 2 my get is:
router.get('/page-two/', function (req, res) {
res.render('/page-two/', {
})
})
I'm using Express, Express Session, Express Writer and Nunjucks.

Redirect all requests if condition satisfied using middleware

I have an instance of expressjs server running that serves an SPA.
My API lives on another server so I need to bounce my API requests.
I want to create a middleware that redirects all those requests to another host if it meets a certain condition.
The requests that are gonna be handled are the ones with target: API in the request headers
so far this is how my middleware looks like and I am able to capture selected request which meets the condition:
function apiHelmet(req, res, next) {
if (req.get('target') === 'API') {
/*
find a way to redirect requests of all methods and pipe result to response
*/
return res.send(response_from_external_api)
}
return next()
}
my setup:
app.use(apiHelmet)
app.get('*', renderSPA)
example post request:
axios({
method: 'POST',
data: { id: 123, first_name: 'john', last_name: 'doe' },
url: '/users',
headers: {
target: 'API'
}
} ).then(result=>console.log(result))
Try this, Call another External API using request module and pipe External API response directly to Your server response.
var request = require('request');
app.use(apiHelmet)
app.get('*', function (req, res, next) {
req.pipe(request('SOME URL').on('error', function (err) {
return res.status(400).send(err)
})).pipe(res);
});
You can do this easily using following code.
function apiHelmet(req, res, next) {
if (req.get('target') === 'API') {
return res.send('caught ya')
req.post({url: 'http://end-point', headers: req.headers, body: req.body});
processRequest(req);
res.setHeader('Content-Type', 'application/json');
res.send('Req OK');
}
return next()
}
For more details see.

Hapi.js - adding mechanism to check every route

I am trying to implement a mechanism that will be run before any route is hit. In that mechanism I want to take a value from the header and check for authentication.
I have come up with this:
server.js:
// Create a server with a host and port
'use strict';
var Hapi = require('hapi');
var mongojs = require('mongojs');
var plugins = [
require('./routes/entities')
];
var server = new Hapi.Server();
server.connection({
port: 3000
});
//Connect to db
server.app.db = mongojs('hapi-rest-mongo', ['entities']);
server.app.checkHeader = function (request) {
var header = request.headers['x-authorization'];
if(header === "letmein"){
return true
}
return false
};
//Load plugins and start server
server.register(plugins, function (err) {
if (err) {
throw err;
}
// Start the server
server.start(function (err) {
console.log('Server running at:', server.info.uri);
});
});
and in routes.entities:
'use strict';
var Boom = require('boom');
var uuid = require('node-uuid');
var Joi = require('joi');
exports.register = function (server, options, next) {
var db = server.app.db;
server.route({
method: 'GET',
path: '/entities',
handler: function handler(request, reply) {
if(!server.app.checkHeader(request))
{
return reply(Boom.unauthorized());
};
//request.server.myFunc();
db.entities.find(function (err, docs) {
if (err) {
return reply(Boom.wrap(err, 'Internal MongoDB error'));
}
reply(docs);
});
}
});
So in short while starting the server I have registered my function server.app.checkHeader
And in the routes I am calling it and sending a request object to it. Request object contains information about the headers.
While this works, I am having a feeling I am not following the best practices with the Hapi.
How could I do it more elegantly?
There are a few options.
You can, of course, tap into the request lifecycle - note the events that occur in the pipeline prior to the route handler.
Although, I'd urge you to consider implementing an auth strategy that can be set as the default for all routes or selectively on appropriate routes.
The best way to require authentication for all or selected route is to use hapi’s integrated functionality.
You should set a default authentication strategy that is applied to each route handler. The sample below uses basic auth. You’d want to create a custom authentication strategy for hapi to check your x-authentication header.
const Hapi = require('hapi')
const BasicAuth = require('hapi-auth-basic')
const server = new Hapi.Server()
server.register(BasicAuth, function (err) {
if (err) {
console.log('error', 'failed to install plugins')
throw err
}
// TODO: add authentication strategy & set as default
server.auth.strategy('simple', 'basic', true, { validateFunc: basicValidationFn })
// or set strategy separately as default auth strategy
server.auth.strategy('simple', 'basic', { validateFunc: basicValidationFn })
server.auth.default('simple')
// TODO: add routes
server.start(function (err) {
})
})
You can also inject hapi’s request lifecycle and extend it at given points. Extending the request lifecycle should be done by using plugins:
register: function (server, options, next) {
// do some processing before 'onPreAuth'
// or pick another extension point
server.ext('onPreAuth', (request, reply) => {
// your functionality
})
}
Hope that helps!

Strongloop Loopback remote hooks not triggered with supertest?

We are testing our loopback API code using spec.js files like this:
Require libs:
var app = rewire('../..');
var request = require('supertest');
var assert = require('chai').assert;
json helper method to standardize headers and content type:
function json(verb, url) {
return request(app)[verb](url)
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.expect('Content-Type', /json/);
}
A test of a custom remote method that requires auth:
describe("Order remote methods", function() {
var accessTokenId, userId;
// authenticate before each test and save token
before(function(done) {
json('post', '/api/People/login')
.send({ email: 'user#email.com', password: 'password' })
.expect(200)
.end(function(err, res) {
accessTokenId = res.body.id;
userId = res.body.userId;
assert(res.body.id);
assert(res.body.userId);
done();
});
});
it("should fetch user orders", function(done) {
json('get', '/api/Orders/specialOrders')
.set('Authorization', accessTokenId)
.send({id: userId})
.expect(200)
.end(function(err, res) {
var orders = res.body.orders;
assert(Array.isArray(orders), "Orders should be an array");
// more asserts for explicit data values
done();
});
});
});
/api/Orders/specialOrders is a custom remote method that does a custom query on the Order model, which works as expected. But when I add a beforeRemote hook for this model, it does not get triggered by running the test. Is this expected or is my test setup not complete?
Remote hook:
Order.beforeRemote('specialOrders', function(ctx, unused, next) {
console.log('[userOrders]');
console.log('ctx req token: ', ctx.req.accessToken.userId);
console.log('ctx args: ', ctx.req.params.id);
// prevent remote method from being called
// even without a next(), remote is executed!
next(new Error('testing error'));
});
Running the same custom method via the Explorer UI, the beforeRemote hook is triggered as expected, and reports the custom error (or hangs when the next() is not present).
Is it possible to get supertest to trigger remote hooks in tests like this or am I missing some app setup in the spec file?

Secure POST request from NodeJS to Node/Express hangs

I'm using the following to send POST data to a secure nodejs server:
File: main.js
var strdata = JSON.stringify({"data":"thisdata"});
var options = {
host: '192.168.1.63',
port: 3001,
path: '/saveconfig',
method: 'POST',
rejectUnauthorized: false,
requestCert: true,
agent: false,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(strdata)
}
};
var req = https.request(options, function(res) {
console.log('STATUS: ' + res.statusCode);
console.log('HEADERS: ' + JSON.stringify(res.headers));
res.setEncoding('utf8');
res.on('data', function (chunk) {
console.log('BODY: ' + chunk);
});
});
console.log(req.write(strdata));
console.log(req.end());
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
req.on('finish', function() {
console.log("finished request");
});
In an otherwise functional expressjs server, these are the appropriate snippets:
File: app.js
app.post('/saveconfig', function() {
data.saveconfig; console.log("received request"); } );
app.get('/getconfig', data.getconfig);
File: data.js
exports.saveconfig = function(req, res) {
console.log("saveing config");
res.send(200);
res.end();
};
exports.getconfig = function(req, res) {
res.send("get OK");
}
With app.js running on the server (Ubuntu), I run main.js from the client (Windows 7). req.write and req.end execute and "finished request" logs to the console, but the request callback never fires.
On the server in app.js, the app.post event fires and logs "received request" to the console. But "saving config" never logs to the console. Only after I kill (^C) main.js, express then logs to the console "POST /saveconfig".
I know I'm missing something simple, but I've read dozens of coding examples and you can likely gather from my snippet, I've tried everything I can find or think of. I'd guess the request isn't finishing, but I don't know why. What is missing to get "exports.saveconfig" to fire?
additional information
The answer posted below fixed my problem. Because I'm new to stackoverflow, I can't post my own answer, but here's the rest of the story...
I appreciate your help. Being still new to JavaScript, I found I can learn a lot about an object by converting it to string. I was originally attempting to convert the req parameter to a string using a custom function. I just discovered it was apparently running into an endless loop after using JSON.stringify instead.
The code looked something like this:
exports.saveconfig = function (db) {
return function(req, res) {
console.log("saving config");
console.log(mymodule.serialize(req));
res.end("OK");
console.log(req.body);
};
};
I would have thought the above code should have logged the following to the console- even if the serialize method was in an endless loop:
POST /saveconfig
saving config
[nothing because of the endless loop]
Instead I got:
saving config
connections property is deprecated. Use getConnections() method
Being new to JavaScript, I assumed something was wrong with the request, the server, or some plumbing in-between. Of course, then the debugging code I added (along with my ignorance of JS) compounded the problem.
Changing
app.post('/saveconfig', function() {
data.saveconfig; console.log("received request"); } );
to
app.post('/saveconfig', datarts.saveconfig);
and removing the endless loop fixed the problem.
The problem is in you're app.js. You use data.saveConfig inside your callback without calling it. The right way would be
app.post('/saveconfig', function(req,res) {
data.saveconfig(req, res);
console.log("received request");
});
app.get('/getconfig', data.getconfig);
or (I assume the console.log is just for debugging purposes):
app.post('/saveconfig', data.saveconfig);
app.get('/getconfig', data.getconfig);
You could do your console.log() inside your data.saveconfig method if you want to go with the second example.

Categories