Can't add templating engine in plugin HapiJS - javascript

I want to implement routes per plugin, but I can't add the views engine inside the plugin. I've seen examples where this is possible, E.G.: https://github.com/hapijs-edge/hapi-plugins.com/blob/master/lib/routes.js, but I'm getting an error saying server.views is not a function
var Hapi = require('hapi');
var server = new Hapi.Server();
server.connection();
var myPlugin = {
register: function (server, options, next) {
// Error happens here, should be able to see server.views()
console.log(server.views());
next();
}
};
myPlugin.register.attributes = {
name: 'myPlugin',
version: '1.0.0'
};
server.register( myPlugin, function(err) {
if (err) {
console.error('Failed to load a plugin:', err);
}
} );
server.start(function () {
console.log('Server running at:', server.info.uri);
});

It seems to be a problem with hapi v10. Try "npm i hapi#8.8.1", that version should work

The guys at hapi, showed me the way... as of hapi 9, vision module is required to decorate server and have access to the views method. It now works fine!

You need to register vision plugin before you can use server.view function as of hapi => 9.x.x.

Related

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!

Centralized error handling for Resitify

I'm having trouble getting centralized error handling set up in my restify app. I'd like to trap certain Mongo errors, such as "E11000 duplicate key error" and then map them to a restify ConflictError.
If I just let the error bubble up from my Mongo call in a route, the client gets a 500 error.
I figured I should trap InternalServerError, but the below handler never gets called:
app.on('InternalServerError', function (req, res, err, cb) {
console.log('++++++++++++++++', err);
return cb(err);
});
I thought I could just use the express approach:
app.use(function (err, req, res, next){...
But restify handlers don't seem to take an error argument. I'm stumped after searching all the usual places. It seems my first approach should have just worked.
This might work for you. Set up a bunyan logger in your app.js file…
var bunyan = require('bunyan');
var log = new bunyan({
name: 'my_api',
streams: [
{
path: './error.log',
level: 'warn'
}
],
serializers: {req: restify.bunyan.serializers.req},
src: false
});
var server = restify.createServer({
log: log
});
Then in your controller do something like this….
var restify = require('restify');
try {
Model.findAll().then(function(vals){
res.send(vals);
next();
});
}
catch(e) {
req.log.error({req_id: req.id()}, 'Error attempting find.');
res.send(409, new restify.ConflictError("Problem executing search."));
next();
}

Unable to receive upstream GCM messages with Node.js + XMPP

I'm new to node.js and XMPP but not Javascript or GCM. I'm unable to receive upstream messages using node-xmpp and none of the callbacks are called, not even error. I've looked through the other SO threads but none of the solutions have worked. Here is my entire route:
var express = require('express');
var router = express.Router();
var xmpp = require('node-xmpp');
router.get('/', function(req, res, next) {
var options = {
type: 'client',
jid: 'project-12345#gcm.googleapis.com',
password: 'apiKey12345',
port: 5235,
host: 'gcm.googleapis.com',
legacySSL: true,
preferredSaslMechanism : 'PLAIN'
};
// this prints correctly
console.log('Creating xmpp app');
var cl = new xmpp.Client(options);
cl.connection.socket.setTimeout(0);
cl.connection.socket.setKeepAlive(true, 10000);
// None of these callbacks are called
cl.on('online', function() {
console.log('online');
});
cl.on('connection', function() {
console.log('online');
});
cl.on('authenticate', function(opts, cb) {
console.log('authenticated');
});
cl.on('error',function(e) {
console.error(e);
});
cl.on('stanza', function(stanza) {
console.log(stanza);
});
res.render('index', { title: 'GCM upstream test' });
});
module.exports = router;
Thanks
OP here: The issue was because the route was terminating the XMPP action upon reaching res.render. Upon removing the XMPP code from the route, I get an XMPP authentication failure, which is most likely due to an incorrect jid/password. The project requirements have changed and I no longer need upstream messaging, so I will not attempt to fix the auth failure.
Thanks for the responses

LoopBack: cannot call method 'post' of undefined

I am new to loopback and node.js.
I have created two models: Rating and RatingsAggregate
using the loopback explorer, I can query and post against the API just fine.
I am try to setup some basic business logic so I am editing the file Rating.js in common/models
Here is the content of it:
module.exports = function(Rating) {
Rating.afterRemote('**', function(ctx, inst, next) {
var loopback = require('loopback');
var app = loopback();
var ratingsaggregate = app.models.ratingsaggregate;
ratingsaggregate.post({"source":"foobar","restaurantID":"foobar","itemMenuName":"foobar","itemSectionName":"foobar","itemName":"foobar","nRatings1":123,"nRatings2":123,"nRatings3":123,"nRatings4":123,"nRatings5":123,"hasImage":true,"imageSize":123,"latestImageRatingID":"foobar","imageCount":123,"lastUpdated":"foobar"}, function(err, response) {
if (err) console.error(err);
next();
});
});
};
I can load my API, but whenever I run a get statement against it, I get this error:
TypeError: Cannot call method 'post' of undefined
My guess is that somehow ratingsaggregate never gets a value... but I don't know what I am doing wrong. Obviously this is not the end state of my business logic, but I am trying some basic CRUD right now between two models
And... here is the answer. There was a getModel function hidden in the documentation
module.exports = function(Rating) {
Rating.afterRemote('create', function(ctx, inst, next) {
var loopback = require('loopback');
var ratingsaggregate = loopback.getModel('ratingsaggregate');
ratingsaggregate.create({"source":"foobar","restaurantID":"foobar","itemMenuName":"foobar","itemSectionName":"foobar","itemName":"foobar","nRatings1":123,"nRatings2":123,"nRatings3":123,"nRatings4":123,"nRatings5":123,"hasImage":true,"imageSize":123,"latestImageRatingID":"foobar","imageCount":123,"lastUpdated":"foobar"}, function(err, response) {
if (err) console.error(err);
next();
});
});
};
Fixes everything and the behaviour is the expected one

Mongoose open connection issue with Supertest

I am unable to run multiple Supertest/Mocha tests as I get an error Error: Trying to open unclosed connection. - I found this post which suggest looping and checking connection status. Wondering if there is a better way? perhaps something that was added in Supertest recently to handle this.
In your Mocha tests add a before function to connect to MongoDB like so
var mongoose = require('mongoose');
describe('My test', function() {
before(function(done) {
if (mongoose.connection.db) return done();
mongoose.connect('mongodb://localhost/puan_test', done);
});
});
Ok - was pretty close. What I had to do was remove the describe method call and place a before() call in a common file to all tests - supertest or just straight mocha unit tests.
var db;
// Once before all tests - Supertest will have a connection from the app already while others may not
before(function(done) {
if (mongoose.connection.db) {
db = mongoose.connection;
return done();
}
db = mongoose.connect(config.db, done);
});
// and if I wanted to load fixtures before each test
beforeEach(function (done) {
fixtures.load(data, db, function(err) {
if (err) throw (err);
done();
})
});
By omitting the describe() call the above it makes it available to all tests.
// Also you can use the 'open' event to call the 'done' callback
// inside the 'before' Mocha hook.
before((done) => {
mongoose.connect('mongodb://localhost/test_db');
mongoose.connection
.once('open', () => {
done();
})
.on('error', (err) => {
console.warn('Problem connecting to mongo: ', error);
done();
});
});

Categories